/**
 @file debugger.cpp

 @brief Implements the debugger class.
 */

#include "debugger.h"

#include "TraceRecord.h"
#include "animate.h"
#include "breakpoint.h"
#include "cmd-watch-control.h"
#include "command.h"
#include "commandline.h"
#include "console.h"
#include "database.h"
#include "dbghelp_safe.h"
#include "debugger_cookie.h"
#include "debugger_tracing.h"
#include "exception.h"
#include "exprfunc.h"
#include "filemap.h"
#include "handle.h"
#include "historycontext.h"
#include "jit.h"
#include "memory.h"
#include "module.h"
#include "plugin_loader.h"
#include "simplescript.h"
#include "stackinfo.h"
#include "stringformat.h"
#include "symbolinfo.h"
#include "taskthread.h"
#include "thread.h"
#include "threading.h"
#include "variable.h"
#include "watch.h"
#include "x64dbg.h"
#include "zydis_wrapper.h"

// Debugging variables
static PROCESS_INFORMATION g_pi = {0, 0, 0, 0};
static char szBaseFileName[MAX_PATH] = "";
static TraceState traceState;
static bool bFileIsDll = false;
static bool bEntryIsInMzHeader = false;
static duint pDebuggedBase = 0;
static duint pCreateProcessBase = 0;
static duint pDebuggedEntry = 0;
static bool bRepeatIn = false;
static duint stepRepeat = 0;
static bool isDetachedByUser = false;
static bool bIsAttached = false;
static bool bSkipExceptions = false;
static duint skipExceptionCount = 0;
static bool bFreezeStack = false;
static std::vector<ExceptionRange> ignoredExceptionRange;
static HANDLE hEvent = 0;
static duint tidToResume = 0;
static HANDLE hMemMapThread = 0;
static bool bStopMemMapThread = false;
static HANDLE hTimeWastedCounterThread = 0;
static bool bStopTimeWastedCounterThread = false;
static HANDLE hDumpRefreshThread = 0;
static bool bStopDumpRefreshThread = false;
static String lastDebugText;
static duint timeWastedDebugging = 0;
static EXCEPTION_DEBUG_INFO lastExceptionInfo = {0};
static char szDebuggeeInitializationScript[MAX_PATH] = "";
static WString gInitExe, gInitCmd, gInitDir, gDllLoader;
static CookieQuery cookie;
static duint exceptionDispatchAddr = 0;
static bool bPausedOnException = false;
static HANDLE DebugDLLFileMapping = 0;
char szProgramDir[MAX_PATH] = "";
char szDebuggeePath[MAX_PATH] = "";
char szDllLoaderPath[MAX_PATH] = "";
char szSymbolCachePath[MAX_PATH] = "";
std::vector<std::pair<duint, duint>> RunToUserCodeBreakpoints;
PROCESS_INFORMATION* fdProcessInfo = &g_pi;
HANDLE hActiveThread;
HANDLE hProcessToken;
bool bUndecorateSymbolNames = true;
bool bEnableSourceDebugging = false;
bool bTraceRecordEnabledDuringTrace = true;
bool bSkipInt3Stepping = false;
bool bIgnoreInconsistentBreakpoints = false;
bool bNoForegroundWindow = false;
bool bVerboseExceptionLogging = true;
bool bNoWow64SingleStepWorkaround = false;
bool bTraceBrowserNeedsUpdate = false;
bool bForceLoadSymbols = false;
duint DbgEvents = 0;
duint maxSkipExceptionCount = 0;
HANDLE mProcHandle;
HANDLE mForegroundHandle;
duint mRtrPreviousCSP = 0;
HANDLE hDebugLoopThread = nullptr;
DWORD dwDebugFlags = 0;

static duint dbgcleartracestate() {
  auto steps = traceState.StepCount();
  traceState.Clear();
  return steps;
}

static void dbgClearRtuBreakpoints() {
  EXCLUSIVE_ACQUIRE(LockRunToUserCode);
  for (auto& i : RunToUserCodeBreakpoints) {
    BREAKPOINT bp;
    if (!BpGet(i.first, BPMEMORY, nullptr, &bp))
      RemoveMemoryBPX(i.first, i.second);
  }
  RunToUserCodeBreakpoints.clear();
}

bool dbgsettracecondition(const String& expression, duint maxSteps) {
  if (dbgtraceactive()) return false;
  if (!traceState.InitTraceCondition(expression, maxSteps)) return false;
  if (traceState.InitLogFile()) return true;
  dbgcleartracestate();
  return false;
}

bool dbgsettracelog(const String& expression, const String& text) {
  if (dbgtraceactive()) return false;
  return traceState.InitLogCondition(expression, text);
}

bool dbgsettracecmd(const String& expression, const String& text) {
  if (dbgtraceactive()) return false;
  return traceState.InitCmdCondition(expression, text);
}

bool dbgsettraceswitchcondition(const String& expression) {
  if (dbgtraceactive()) return false;
  return traceState.InitSwitchCondition(expression);
}

bool dbgtraceactive() { return traceState.IsActive(); }

void dbgforcebreaktrace() {
  if (traceState.IsActive()) traceState.SetForceBreakTrace();
}

bool dbgsettracelogfile(const char* fileName) {
  traceState.SetLogFile(fileName);
  return true;
}

static DWORD WINAPI memMapThread(void* ptr) {
  while (!bStopMemMapThread) {
    while (!DbgIsDebugging()) {
      if (bStopMemMapThread) break;
      Sleep(10);
    }
    if (bStopMemMapThread) break;
    MemUpdateMapAsync();
    ThreadUpdateWaitReasons();
    GuiUpdateThreadView();
    Sleep(2000);
  }

  return 0;
}

static bool isUserIdle() {
  LASTINPUTINFO lii;
  lii.cbSize = sizeof(LASTINPUTINFO);
  GetLastInputInfo(&lii);
  return GetTickCount() - lii.dwTime >
         1000 * 60;  // 60 seconds without input is considered idle
}

static DWORD WINAPI timeWastedCounterThread(void* ptr) {
  if (!BridgeSettingGetUint("Engine", "TimeWastedDebugging",
                            &timeWastedDebugging))
    timeWastedDebugging = 0;
  GuiUpdateTimeWastedCounter();
  while (!bStopTimeWastedCounterThread) {
    while (!DbgIsDebugging() || isUserIdle()) {
      if (bStopTimeWastedCounterThread) break;
      Sleep(10);
    }
    if (bStopTimeWastedCounterThread) break;
    timeWastedDebugging++;
    GuiUpdateTimeWastedCounter();
    Sleep(1000);
  }
  BridgeSettingSetUint("Engine", "TimeWastedDebugging", timeWastedDebugging);
  return 0;
}

static DWORD WINAPI dumpRefreshThread(void* ptr) {
  while (!bStopDumpRefreshThread) {
    while (!DbgIsDebugging()) {
      if (bStopDumpRefreshThread) break;
      Sleep(100);
    }
    if (bStopDumpRefreshThread) break;
    GuiUpdateDumpView();
    GuiUpdateWatchView();
    if (bTraceBrowserNeedsUpdate) {
      bTraceBrowserNeedsUpdate = false;
      GuiUpdateTraceBrowser();
    }
    Sleep(400);
  }
  return 0;
}

/**
\brief Called when the debugger pauses.
*/
void cbDebuggerPaused() {
  // Clear tracing conditions
  dbgcleartracestate();
  dbgClearRtuBreakpoints();
  mRtrPreviousCSP = 0;
  // Trace record is not handled by this function currently.
  // Signal thread switch warning
  if (settingboolget("Engine", "HardcoreThreadSwitchWarning")) {
    static DWORD PrevThreadId = 0;
    if (PrevThreadId == 0)
      PrevThreadId = fdProcessInfo->dwThreadId;  // Initialize to Main Thread
    DWORD currentThreadId = ThreadGetId(hActiveThread);
    if (currentThreadId != PrevThreadId && PrevThreadId != 0) {
      dprintf(QT_TRANSLATE_NOOP("DBG", "Thread switched from %X to %X !\n"),
              PrevThreadId, currentThreadId);
      PrevThreadId = currentThreadId;
    }
  }
  // Watchdog
  cbCheckWatchdog(0, nullptr);
}

void dbginit() {
  hTimeWastedCounterThread =
      CreateThread(nullptr, 0, timeWastedCounterThread, nullptr, 0, nullptr);
  hMemMapThread = CreateThread(nullptr, 0, memMapThread, nullptr, 0, nullptr);
  hDumpRefreshThread =
      CreateThread(nullptr, 0, dumpRefreshThread, nullptr, 0, nullptr);
}

void dbgstop() {
  bStopTimeWastedCounterThread = true;
  bStopMemMapThread = true;
  bStopDumpRefreshThread = true;
  HANDLE hThreads[] = {hTimeWastedCounterThread, hMemMapThread,
                       hDumpRefreshThread};
  WaitForMultipleThreadsTermination(hThreads, _countof(hThreads),
                                    10000);  // Total time out is 10 seconds.
}

duint dbgdebuggedbase() { return pDebuggedBase; }

duint dbggettimewastedcounter() { return timeWastedDebugging; }

bool dbgisrunning() { return !waitislocked(WAITID_RUN); }

bool dbgisdll() { return bFileIsDll; }

void dbgsetattachevent(HANDLE handle) { hEvent = handle; }

void dbgsetresumetid(duint tid) { tidToResume = tid; }

void dbgsetskipexceptions(bool skip) {
  bSkipExceptions = skip;
  skipExceptionCount = 0;
}

void dbgsetsteprepeat(bool steppingIn, duint repeat) {
  bRepeatIn = steppingIn;
  stepRepeat = repeat;
}

void dbgsetisdetachedbyuser(bool b) { isDetachedByUser = b; }

void dbgsetfreezestack(bool freeze) { bFreezeStack = freeze; }

void dbgclearignoredexceptions() { ignoredExceptionRange.clear(); }

void dbgaddignoredexception(ExceptionRange range) {
  ignoredExceptionRange.push_back(range);
}

bool dbgisignoredexception(unsigned int exception) {
  for (unsigned int i = 0; i < ignoredExceptionRange.size(); i++) {
    unsigned int curStart = ignoredExceptionRange.at(i).start;
    unsigned int curEnd = ignoredExceptionRange.at(i).end;
    if (exception >= curStart && exception <= curEnd) return true;
  }
  return false;
}

bool dbgcmdnew(const char* name, CBCOMMAND cbCommand, bool debugonly) {
  if (!cmdnew(name, cbCommand, debugonly)) return false;
  GuiAutoCompleteAddCmd(name);
  return true;
}

bool dbgcmddel(const char* name) {
  if (!cmddel(name)) return false;
  GuiAutoCompleteDelCmd(name);
  return true;
}

duint dbggetdbgevents() {
  return InterlockedExchange((volatile long*)&DbgEvents, 0);
}

void dbgtracebrowserneedsupdate() { bTraceBrowserNeedsUpdate = true; }

static std::unordered_map<std::string, std::pair<DWORD, bool>> dllBreakpoints;

bool dbgsetdllbreakpoint(const char* mod, DWORD type, bool singleshoot) {
  EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
  return dllBreakpoints.insert({mod, {type, singleshoot}}).second;
}

bool dbgdeletedllbreakpoint(const char* mod, DWORD type) {
  EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
  auto found = dllBreakpoints.find(mod);
  if (found == dllBreakpoints.end()) return false;
  dllBreakpoints.erase(found);
  return true;
}

void dbgsetdebugflags(DWORD flags) { dwDebugFlags = flags; }

bool dbghandledllbreakpoint(const char* mod, bool loadDll) {
  EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
  auto shouldBreak = false;
  auto found = dllBreakpoints.find(mod);
  if (found != dllBreakpoints.end()) {
    if (found->second.first == UE_ON_LIB_ALL ||
        found->second.first == (loadDll ? UE_ON_LIB_LOAD : UE_ON_LIB_UNLOAD))
      shouldBreak = true;
    if (found->second.second) dllBreakpoints.erase(found);
  }
  return shouldBreak;
}

static DWORD WINAPI updateCallStackThread(duint ptr) {
  stackupdatecallstack(ptr);
  GuiUpdateCallStack();
  return 0;
}

void updateCallStackAsync(duint ptr) {
  static TaskThread_<decltype(&updateCallStackThread), duint>
      updateCallStackTask(&updateCallStackThread);
  updateCallStackTask.WakeUp(ptr);
}

DWORD WINAPI updateSEHChainThread() {
  GuiUpdateSEHChain();
  stackupdateseh();
  GuiUpdateDumpView();
  return 0;
}

void updateSEHChainAsync() {
  static TaskThread_<decltype(&updateSEHChainThread)> updateSEHChainTask(
      &updateSEHChainThread);
  updateSEHChainTask.WakeUp();
}

static void DebugUpdateTitle(duint disasm_addr, bool analyzeThreadSwitch) {
  char modname[MAX_MODULE_SIZE] = "";
  char modtext[MAX_MODULE_SIZE * 2] = "";
  if (!ModNameFromAddr(disasm_addr, modname, true))
    *modname = 0;
  else
    sprintf_s(modtext,
              GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Module: %s - ")),
              modname);
  char threadswitch[256] = "";
  DWORD currentThreadId = ThreadGetId(hActiveThread);
  if (analyzeThreadSwitch) {
    static DWORD PrevThreadId = 0;
    if (PrevThreadId == 0)
      PrevThreadId = fdProcessInfo->dwThreadId;  // Initialize to Main Thread
    if (currentThreadId != PrevThreadId && PrevThreadId != 0) {
      char threadName2[MAX_THREAD_NAME_SIZE] = "";
      if (!ThreadGetName(PrevThreadId, threadName2) || threadName2[0] == 0)
        sprintf_s(threadName2, "%X", PrevThreadId);
      sprintf_s(
          threadswitch,
          GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (switched from %s)")),
          threadName2);
      PrevThreadId = currentThreadId;
    }
  }
  char title[deflen] = "";
  char threadName[MAX_THREAD_NAME_SIZE + 1] = "";
  if (ThreadGetName(currentThreadId, threadName) && *threadName)
    strcat_s(threadName, " ");
  char PIDnumber[64], TIDnumber[64];
  if (settingboolget("Gui", "PidInHex")) {
    sprintf_s(PIDnumber, "%X", fdProcessInfo->dwProcessId);
    sprintf_s(TIDnumber, "%X", currentThreadId);
  } else {
    sprintf_s(PIDnumber, "%u", fdProcessInfo->dwProcessId);
    sprintf_s(TIDnumber, "%u", currentThreadId);
  }
  sprintf_s(title,
            GuiTranslateText(
                QT_TRANSLATE_NOOP("DBG", "%s - PID: %s - %sThread: %s%s%s")),
            szBaseFileName, PIDnumber, modtext, threadName, TIDnumber,
            threadswitch);
  GuiUpdateWindowTitle(title);
}

void DebugUpdateGui(duint disasm_addr, bool stack) {
  if (GuiIsUpdateDisabled()) return;
  duint cip = GetContextDataEx(hActiveThread, UE_CIP);
  // Check if the addresses are in the memory map and force update if they are
  // not
  if (!MemIsValidReadPtr(disasm_addr, true) || !MemIsValidReadPtr(cip, true))
    MemUpdateMap();
  else
    MemUpdateMapAsync();
  if (MemIsValidReadPtr(disasm_addr)) {
    if (bEnableSourceDebugging) {
      char szSourceFile[MAX_STRING_SIZE] = "";
      int line = 0;
      if (SymGetSourceLine(cip, szSourceFile, &line))
        GuiLoadSourceFileEx(szSourceFile, cip);
    }
    GuiDisasmAt(disasm_addr, cip);
  }
  duint csp = GetContextDataEx(hActiveThread, UE_CSP);
  if (stack) DebugUpdateStack(csp, csp);
  static volatile duint cacheCsp = 0;
  if (csp != cacheCsp) {
#ifdef _WIN64
    InterlockedExchange((volatile unsigned long long*)&cacheCsp, csp);
#else
    InterlockedExchange((volatile unsigned long*)&cacheCsp, csp);
#endif  //_WIN64
    updateCallStackAsync(csp);
    updateSEHChainAsync();
  }
  DebugUpdateTitle(disasm_addr, true);
  GuiUpdateRegisterView();
  GuiUpdateDisassemblyView();
  GuiUpdateThreadView();
  GuiUpdateSideBar();
}

void GuiSetDebugStateAsync(DBGSTATE state) {
  GuiSetDebugStateFast(state);
  static TaskThread_<decltype(&GuiSetDebugState), DBGSTATE>
      GuiSetDebugStateTask(&GuiSetDebugState, 300);
  GuiSetDebugStateTask.WakeUp(state);
}

void DebugUpdateGuiAsync(duint disasm_addr, bool stack) {
  static TaskThread_<decltype(&DebugUpdateGui), duint, bool> DebugUpdateGuiTask(
      &DebugUpdateGui);
  DebugUpdateGuiTask.WakeUp(disasm_addr, stack);
}

void DebugUpdateTitleAsync(duint disasm_addr, bool analyzeThreadSwitch) {
  static TaskThread_<decltype(&DebugUpdateTitle), duint, bool>
      DebugUpdateTitleTask(&DebugUpdateTitle);
  DebugUpdateTitleTask.WakeUp(disasm_addr, analyzeThreadSwitch);
}

void DebugUpdateGuiSetStateAsync(duint disasm_addr, bool stack,
                                 DBGSTATE state) {
  // call paused routine to clean up various tracing states.
  if (state == paused) cbDebuggerPaused();
  GuiSetDebugStateAsync(state);
  DebugUpdateGuiAsync(disasm_addr, stack);
}

void DebugUpdateBreakpointsViewAsync() {
  static TaskThread_<decltype(&GuiUpdateBreakpointsView)>
      BreakpointsUpdateGuiTask(&GuiUpdateBreakpointsView);
  BreakpointsUpdateGuiTask.WakeUp();
}

void DebugUpdateStack(duint dumpAddr, duint csp, bool forceDump) {
  if (GuiIsUpdateDisabled()) return;
  if (!forceDump && bFreezeStack) dumpAddr = 0;
  GuiStackDumpAt(dumpAddr, csp);
  GuiUpdateArgumentWidget();
}

static void printSoftBpInfo(const BREAKPOINT& bp) {
  auto bptype = "INT3";
  int titantype = bp.titantype;
  if ((titantype & UE_BREAKPOINT_TYPE_UD2) == UE_BREAKPOINT_TYPE_UD2)
    bptype = "UD2";
  else if ((titantype & UE_BREAKPOINT_TYPE_LONG_INT3) ==
           UE_BREAKPOINT_TYPE_LONG_INT3)
    bptype = "LONG INT3";
  auto symbolicname = SymGetSymbolicName(bp.addr);
  if (symbolicname.length()) {
    if (*bp.name)
      dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint \"%s\" at %s (%p)!\n"),
              bptype, bp.name, symbolicname.c_str(), bp.addr);
    else
      dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint at %s (%p)!\n"), bptype,
              symbolicname.c_str(), bp.addr);
  } else {
    if (*bp.name)
      dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint \"%s\" at %p!\n"), bptype,
              bp.name, bp.addr);
    else
      dprintf(QT_TRANSLATE_NOOP("DBG", "%s breakpoint at %p!\n"), bptype,
              bp.addr);
  }
}

static void printHwBpInfo(const BREAKPOINT& bp) {
  const char* bpsize = "";
  switch (TITANGETSIZE(bp.titantype))  // size
  {
    case UE_HARDWARE_SIZE_1:
      bpsize = "byte, ";
      break;
    case UE_HARDWARE_SIZE_2:
      bpsize = "word, ";
      break;
    case UE_HARDWARE_SIZE_4:
      bpsize = "dword, ";
      break;
#ifdef _WIN64
    case UE_HARDWARE_SIZE_8:
      bpsize = "qword, ";
      break;
#endif  //_WIN64
  }
  char* bptype;
  switch (TITANGETTYPE(bp.titantype))  // type
  {
    case UE_HARDWARE_EXECUTE:
      bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "execute")));
      bpsize = "";
      break;
    case UE_HARDWARE_READWRITE:
      bptype =
          _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "read/write")));
      break;
    case UE_HARDWARE_WRITE:
      bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "write")));
      break;
    default:
      bptype = _strdup(" ");
  }
  auto symbolicname = SymGetSymbolicName(bp.addr);
  if (symbolicname.length()) {
    if (*bp.name)
      dprintf(QT_TRANSLATE_NOOP(
                  "DBG", "Hardware breakpoint (%s%s) \"%s\" at %s (%p)!\n"),
              bpsize, bptype, bp.name, symbolicname.c_str(), bp.addr);
    else
      dprintf(
          QT_TRANSLATE_NOOP("DBG", "Hardware breakpoint (%s%s) at %s (%p)!\n"),
          bpsize, bptype, symbolicname.c_str(), bp.addr);
  } else {
    if (*bp.name)
      dprintf(QT_TRANSLATE_NOOP("DBG",
                                "Hardware breakpoint (%s%s) \"%s\" at %p!\n"),
              bpsize, bptype, bp.name, bp.addr);
    else
      dprintf(QT_TRANSLATE_NOOP("DBG", "Hardware breakpoint (%s%s) at %p!\n"),
              bpsize, bptype, bp.addr);
  }
  free(bptype);
}

static void printMemBpInfo(const BREAKPOINT& bp, const void* ExceptionAddress) {
  char* bptype;
  switch (bp.titantype) {
    case UE_MEMORY_READ:
      bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (read)")));
      break;
    case UE_MEMORY_WRITE:
      bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (write)")));
      break;
    case UE_MEMORY_EXECUTE:
      bptype =
          _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (execute)")));
      break;
    case UE_MEMORY:
      bptype = _strdup(
          GuiTranslateText(QT_TRANSLATE_NOOP("DBG", " (read/write/execute)")));
      break;
    default:
      bptype = _strdup("");
  }
  auto symbolicname = SymGetSymbolicName(bp.addr);
  if (symbolicname.length()) {
    if (*bp.name)
      dprintf(QT_TRANSLATE_NOOP("DBG",
                                "Memory breakpoint%s \"%s\" at %s (%p, %p)!\n"),
              bptype, bp.name, symbolicname.c_str(), bp.addr, ExceptionAddress);
    else
      dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint%s at %s (%p, %p)!\n"),
              bptype, symbolicname.c_str(), bp.addr, ExceptionAddress);
  } else {
    if (*bp.name)
      dprintf(
          QT_TRANSLATE_NOOP("DBG", "Memory breakpoint%s \"%s\" at %p (%p)!\n"),
          bptype, bp.name, bp.addr, ExceptionAddress);
    else
      dprintf(QT_TRANSLATE_NOOP("DBG", "Memory breakpoint%s at %p (%p)!\n"),
              bptype, bp.addr, ExceptionAddress);
  }
  free(bptype);
}

static void printDllBpInfo(const BREAKPOINT& bp) {
  char* bptype;
  switch (bp.titantype) {
    case UE_ON_LIB_LOAD:
      bptype = _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "DLL Load")));
      break;
    case UE_ON_LIB_UNLOAD:
      bptype =
          _strdup(GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "DLL Unload")));
      break;
    case UE_ON_LIB_ALL:
      bptype = _strdup(
          GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "DLL Load and unload")));
      break;
    default:
      bptype = _strdup("");
  }
  if (*bp.name)
    dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Breakpoint %s (%s): Module %s\n"),
            bp.name, bptype, bp.mod);
  else
    dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Breakpoint (%s): Module %s\n"),
            bptype, bp.mod);
  free(bptype);
}

static void printExceptionBpInfo(const BREAKPOINT& bp, duint CIP) {
  if (*bp.name != 0)
    dprintf(QT_TRANSLATE_NOOP("DBG", "Exception Breakpoint %s (%p) at %p!\n"),
            bp.name, bp.addr, CIP);
  else
    dprintf(QT_TRANSLATE_NOOP("DBG", "Exception Breakpoint %s (%p) at %p!\n"),
            ExceptionCodeToName((unsigned int)bp.addr).c_str(), bp.addr, CIP);
}

static bool getConditionValue(const char* expression) {
  auto word = *(uint16*)expression;
  if (word == '0')  // short circuit for condition "0\0"
    return false;
  if (word == '1')  // short circuit for condition "1\0"
    return true;
  duint value;
  if (valfromstring(expression, &value)) return value != 0;
  return true;
}

void cbPauseBreakpoint() {
  dputs(QT_TRANSLATE_NOOP("DBG", "paused!"));
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  auto CIP = GetContextDataEx(hActiveThread, UE_CIP);
  DeleteBPX(CIP);
  DebugUpdateGuiSetStateAsync(CIP, true);
  _dbg_animatestop();  // Stop animating when paused
  // Trace record
  _dbg_dbgtraceexecute(CIP);
  // lock
  lock(WAITID_RUN);
  // Plugin callback
  PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
  plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
  dbgsetforeground();
  dbgsetskipexceptions(false);
  wait(WAITID_RUN);
}

static void handleBreakCondition(const BREAKPOINT& bp,
                                 const void* ExceptionAddress, duint CIP,
                                 bool doBreak) {
  if (doBreak) {
    if (bp.singleshoot) BpDelete(bp.addr, bp.type);
    if (!bp.silent) {
      switch (bp.type) {
        case BPNORMAL:
          printSoftBpInfo(bp);
          break;
        case BPHARDWARE:
          printHwBpInfo(bp);
          break;
        case BPMEMORY:
          printMemBpInfo(bp, ExceptionAddress);
          break;
        case BPDLL:
          printDllBpInfo(bp);
          break;
        case BPEXCEPTION:
          printExceptionBpInfo(bp, CIP);
          break;
        default:
          break;
      }
    }
    DebugUpdateGuiSetStateAsync(CIP, true);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    _dbg_animatestop();  // Stop animating when a breakpoint is hit
  }
}

static void cbGenericBreakpoint(BP_TYPE bptype,
                                void* ExceptionAddress = nullptr) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  auto CIP = GetContextDataEx(hActiveThread, UE_CIP);

  // handle process cookie retrieval
  if (bptype == BPNORMAL && cookie.HandleBreakpoint(CIP)) return;

  BREAKPOINT* bpPtr = nullptr;
  // NOTE: this locking is very tricky, make sure you understand it before
  // modifying anything
  EXCLUSIVE_ACQUIRE(LockBreakpoints);
  duint breakpointExceptionAddress = 0;
  switch (bptype) {
    case BPNORMAL:
      bpPtr = BpInfoFromAddr(BPNORMAL, CIP);
      breakpointExceptionAddress = CIP;
      break;
    case BPHARDWARE:
      bpPtr = BpInfoFromAddr(BPHARDWARE, duint(ExceptionAddress));
      breakpointExceptionAddress = duint(ExceptionAddress);
      break;
    case BPMEMORY:
      bpPtr = BpInfoFromAddr(
          BPMEMORY, MemFindBaseAddr(duint(ExceptionAddress), nullptr, true));
      breakpointExceptionAddress = duint(ExceptionAddress);
      break;
    case BPDLL:
      bpPtr = BpInfoFromAddr(
          BPDLL,
          BpGetDLLBpAddr(reinterpret_cast<const char*>(ExceptionAddress)));
      breakpointExceptionAddress = 0;  // makes no sense
      break;
    case BPEXCEPTION:
      bpPtr =
          BpInfoFromAddr(BPEXCEPTION, ((EXCEPTION_DEBUG_INFO*)ExceptionAddress)
                                          ->ExceptionRecord.ExceptionCode);
      breakpointExceptionAddress =
          (duint)((EXCEPTION_DEBUG_INFO*)ExceptionAddress)
              ->ExceptionRecord.ExceptionAddress;
      break;
    default:
      break;
  }
  varset("$breakpointexceptionaddress", breakpointExceptionAddress, true);
  if (!(bpPtr && bpPtr->enabled))  // invalid / disabled breakpoint hit (most
                                   // likely a bug)
  {
    if (bptype != BPDLL ||
        !BpUpdateDllPath(reinterpret_cast<const char*>(ExceptionAddress),
                         &bpPtr)) {
      // release the breakpoint lock to prevent deadlocks during the wait
      EXCLUSIVE_RELEASE();
      dputs(QT_TRANSLATE_NOOP("DBG", "Breakpoint reached not in list!"));
      DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP),
                                  true);
      // lock
      lock(WAITID_RUN);
      // Plugin callback
      PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
      plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
      dbgsetforeground();
      dbgsetskipexceptions(false);
      wait(WAITID_RUN);
      return;
    }
  }

  // increment hit count
  InterlockedIncrement((volatile long*)&bpPtr->hitcount);

  // copy the breakpoint structure and release the breakpoint lock to prevent
  // deadlocks during the wait
  auto bp = *bpPtr;
  EXCLUSIVE_RELEASE();

  if (bptype != BPDLL && bptype != BPEXCEPTION) bp.addr += ModBaseFromAddr(CIP);
  bp.active = true;  // a breakpoint that has been hit is active

  varset("$breakpointcounter", bp.hitcount,
         true);  // save the breakpoint counter as a variable

  // get condition values
  bool breakCondition;
  bool logCondition;
  bool commandCondition;
  if (*bp.breakCondition)
    breakCondition = getConditionValue(bp.breakCondition);
  else
    breakCondition = true;  // break if no condition is set
  if (bp.fastResume &&
      !breakCondition)  // fast resume: ignore GUI/Script/Plugin/Other if the
                        // debugger would not break
    return;
  if (*bp.logCondition)
    logCondition = getConditionValue(bp.logCondition);
  else
    logCondition = true;  // log if no condition is set
  if (*bp.commandCondition)
    commandCondition = getConditionValue(bp.commandCondition);
  else
    commandCondition = breakCondition;  // if no condition is set, execute the
                                        // command when the debugger would break

  lock(WAITID_RUN);
  handleBreakCondition(bp, ExceptionAddress, CIP, breakCondition);

  PLUG_CB_BREAKPOINT bpInfo;
  BRIDGEBP bridgebp;
  memset(&bridgebp, 0, sizeof(bridgebp));
  bpInfo.breakpoint = &bridgebp;
  BpToBridge(&bp, &bridgebp);
  plugincbcall(CB_BREAKPOINT, &bpInfo);

  // Trace record
  _dbg_dbgtraceexecute(CIP);

  // Watchdog
  cbCheckWatchdog(0, nullptr);

  // Update breakpoint view
  DebugUpdateBreakpointsViewAsync();

  if (*bp.logText && logCondition)  // log
  {
    dprintf_untranslated("%s\n", stringformatinline(bp.logText).c_str());
  }
  if (*bp.commandText && commandCondition)  // command
  {
    // TODO: commands like run/step etc will fuck up your shit
    varset("$breakpointcondition", breakCondition ? 1 : 0, false);
    varset("$breakpointlogcondition", logCondition ? 1 : 0, true);
    cmddirectexec(bp.commandText);
    duint script_breakcondition;
    if (varget("$breakpointcondition", &script_breakcondition, nullptr,
               nullptr)) {
      if (script_breakcondition != 0) {
        handleBreakCondition(bp, ExceptionAddress, CIP, !breakCondition);
        breakCondition = true;
      } else
        breakCondition = false;
    }
  }
  if (breakCondition)  // break the debugger
  {
    dbgsetforeground();
    dbgsetskipexceptions(false);
  } else  // resume immediately
    unlock(WAITID_RUN);

  // wait until the user resumes
  wait(WAITID_RUN);
}

void cbUserBreakpoint() { cbGenericBreakpoint(BPNORMAL); }

void cbHardwareBreakpoint(void* ExceptionAddress) {
  cbGenericBreakpoint(BPHARDWARE, ExceptionAddress);
}

void cbMemoryBreakpoint(void* ExceptionAddress) {
  cbGenericBreakpoint(BPMEMORY, ExceptionAddress);
}

void cbRunToUserCodeBreakpoint(void* ExceptionAddress) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  auto CIP = GetContextDataEx(hActiveThread, UE_CIP);
  auto symbolicname = SymGetSymbolicName(CIP);
  dprintf(QT_TRANSLATE_NOOP("DBG", "User code reached at %s (%p)!"),
          symbolicname.c_str(), CIP);
  // lock
  lock(WAITID_RUN);
  // Trace record
  _dbg_dbgtraceexecute(CIP);
  // Update GUI
  DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
  // Plugin callback
  PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
  plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
  dbgsetforeground();
  dbgsetskipexceptions(false);
  wait(WAITID_RUN);
}

static BOOL CALLBACK SymRegisterCallbackProc64(HANDLE, ULONG ActionCode,
                                               ULONG64 CallbackData, ULONG64) {
  PIMAGEHLP_CBA_EVENT evt;
  switch (ActionCode) {
    case CBA_EVENT: {
      evt = (PIMAGEHLP_CBA_EVENT)CallbackData;
      auto strText = StringUtils::Utf16ToUtf8((const wchar_t*)evt->desc);
      const char* text = strText.c_str();
      if (strstr(text, "Successfully received a response from the server."))
        break;
      if (strstr(text, "Waiting for the server to respond to a request."))
        break;
      int len = (int)strlen(text);
      bool suspress = false;
      for (int i = 0; i < len; i++)
        if (text[i] == 0x08) {
          suspress = true;
          break;
        }
      int percent = 0;
      static bool zerobar = false;
      if (zerobar) {
        zerobar = false;
        GuiSymbolSetProgress(0);
      }
      if (strstr(text, " bytes -  ")) {
        Memory<char*> newtext(len + 1, "SymRegisterCallbackProc64:newtext");
        strcpy_s(newtext(), len + 1, text);
        strstr(newtext(), " bytes -  ")[8] = 0;
        GuiSymbolLogAdd(newtext());
        suspress = true;
      } else if (strstr(text, " copied         ")) {
        GuiSymbolSetProgress(100);
        GuiSymbolLogAdd(" downloaded!\n");
        suspress = true;
        zerobar = true;
      } else if (sscanf_s(text, "%*s %d percent", &percent) == 1 ||
                 sscanf_s(text, "%d percent", &percent) == 1) {
        GuiSymbolSetProgress(percent);
        suspress = true;
      }

      if (!suspress) GuiSymbolLogAdd(text);
    } break;

    case CBA_DEBUG_INFO: {
      GuiSymbolLogAdd((const char*)CallbackData);
    } break;

    default: {
      return FALSE;
    }
  }
  return TRUE;
}

bool cbSetModuleBreakpoints(const BREAKPOINT* bp) {
  if (!bp->enabled) return true;
  switch (bp->type) {
    case BPNORMAL: {
      unsigned short oldbytes;
      if (MemRead(bp->addr, &oldbytes, sizeof(oldbytes))) {
        if (oldbytes != bp->oldbytes && !bIgnoreInconsistentBreakpoints) {
          dprintf(QT_TRANSLATE_NOOP(
                      "DBG",
                      "Breakpoint %p has been disabled because the bytes don't "
                      "match! Expected: %02X %02X, Found: %02X %02X\n"),
                  bp->addr, ((unsigned char*)&bp->oldbytes)[0],
                  ((unsigned char*)&bp->oldbytes)[1],
                  ((unsigned char*)&oldbytes)[0],
                  ((unsigned char*)&oldbytes)[1]);
          BpEnable(bp->addr, BPNORMAL, false);
        } else if (!SetBPX(bp->addr, bp->titantype, (void*)cbUserBreakpoint))
          dprintf(QT_TRANSLATE_NOOP("DBG",
                                    "Could not set breakpoint %p! (SetBPX)\n"),
                  bp->addr);
      } else
        dprintf(QT_TRANSLATE_NOOP("DBG",
                                  "MemRead failed on breakpoint address %p!\n"),
                bp->addr);
    } break;

    case BPMEMORY: {
      duint size = 0;
      MemFindBaseAddr(bp->addr, &size);
      if (!SetMemoryBPXEx(bp->addr, size, bp->titantype, !bp->singleshoot,
                          (void*)cbMemoryBreakpoint))
        dprintf(QT_TRANSLATE_NOOP(
                    "DBG",
                    "Could not set memory breakpoint %p! (SetMemoryBPXEx)\n"),
                bp->addr);
    } break;

    case BPHARDWARE: {
      DWORD drx = 0;
      if (!GetUnusedHardwareBreakPointRegister(&drx)) {
        dputs(QT_TRANSLATE_NOOP("DBG",
                                "You can only set 4 hardware breakpoints"));
        return false;
      }
      int titantype = bp->titantype;
      TITANSETDRX(titantype, drx);
      BpSetTitanType(bp->addr, BPHARDWARE, titantype);
      if (!SetHardwareBreakPoint(bp->addr, drx, TITANGETTYPE(bp->titantype),
                                 TITANGETSIZE(bp->titantype),
                                 (void*)cbHardwareBreakpoint))
        dprintf(QT_TRANSLATE_NOOP("DBG",
                                  "Could not set hardware breakpoint %p! "
                                  "(SetHardwareBreakPoint)\n"),
                bp->addr);
      else
        dprintf(QT_TRANSLATE_NOOP("DBG", "Set hardware breakpoint on %p!\n"),
                bp->addr);
    } break;

    default:
      break;
  }
  return true;
}

bool cbSetDLLBreakpoints(const BREAKPOINT* bp) {
  if (!bp->enabled) return true;
  if (bp->type != BPDLL) return true;
  dbgsetdllbreakpoint(bp->mod, bp->titantype, bp->singleshoot);
  return true;
}

EXCEPTION_DEBUG_INFO& getLastExceptionInfo() { return lastExceptionInfo; }

static bool cbRemoveModuleBreakpoints(const BREAKPOINT* bp) {
  if (!bp->enabled) return true;
  switch (bp->type) {
    case BPNORMAL:
      if (!DeleteBPX(bp->addr))
        dprintf(QT_TRANSLATE_NOOP(
                    "DBG", "Could not delete breakpoint %p! (DeleteBPX)\n"),
                bp->addr);
      break;
    case BPMEMORY:
      if (!RemoveMemoryBPX(bp->addr, 0))
        dprintf(
            QT_TRANSLATE_NOOP(
                "DBG",
                "Could not delete memory breakpoint %p! (RemoveMemoryBPX)\n"),
            bp->addr);
      break;
    case BPHARDWARE:
      if (TITANDRXVALID(bp->titantype) &&
          !DeleteHardwareBreakPoint(TITANGETDRX(bp->titantype)))
        dprintf(QT_TRANSLATE_NOOP("DBG",
                                  "Could not delete hardware breakpoint %p! "
                                  "(DeleteHardwareBreakPoint)\n"),
                bp->addr);
      break;
    default:
      break;
  }
  return true;
}

void DebugRemoveBreakpoints() { BpEnumAll(cbRemoveModuleBreakpoints); }

void DebugSetBreakpoints() { BpEnumAll(cbSetModuleBreakpoints); }

void cbStep() {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  duint CIP = GetContextDataEx(hActiveThread, UE_CIP);
  if (!stepRepeat || !--stepRepeat) {
    DebugUpdateGuiSetStateAsync(CIP, true);
    // Trace record
    _dbg_dbgtraceexecute(CIP);
    // Plugin interaction
    PLUG_CB_STEPPED stepInfo;
    stepInfo.reserved = 0;
    // lock
    lock(WAITID_RUN);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    dbgsetskipexceptions(false);
    plugincbcall(CB_STEPPED, &stepInfo);
    wait(WAITID_RUN);
  } else {
    if (bTraceRecordEnabledDuringTrace) _dbg_dbgtraceexecute(CIP);
    (bRepeatIn ? StepIntoWow64 : StepOverWrapper)((void*)cbStep);
  }
}

static void cbRtrFinalStep(bool checkRepeat = false) {
  if (!checkRepeat || !stepRepeat || !--stepRepeat) {
    dbgcleartracestate();
    hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
    duint CIP = GetContextDataEx(hActiveThread, UE_CIP);
    // Trace record
    _dbg_dbgtraceexecute(CIP);
    DebugUpdateGuiSetStateAsync(CIP, true);
    // lock
    lock(WAITID_RUN);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    dbgsetskipexceptions(false);
    wait(WAITID_RUN);
  } else
    StepOverWrapper((void*)cbRtrStep);
}

void cbRtrStep() {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  unsigned char ch = 0x90;
  duint cip = GetContextDataEx(hActiveThread, UE_CIP);
  duint csp = GetContextDataEx(hActiveThread, UE_CSP);
  MemRead(cip, &ch, 1);
  if (bTraceRecordEnabledDuringTrace) _dbg_dbgtraceexecute(cip);
  if (mRtrPreviousCSP <= csp)  //"Run until return" should break only if RSP is
                               //bigger than or equal to current value
  {
    if (ch == 0xC3 || ch == 0xC2)  // retn instruction
      cbRtrFinalStep(true);
    else if (ch == 0x26 || ch == 0x36 || ch == 0x2e || ch == 0x3e ||
             (ch >= 0x64 && ch <= 0x67) || ch == 0xf2 ||
             ch == 0xf3  // instruction prefixes
#ifdef _WIN64
             || (ch >= 0x40 && ch <= 0x4f)
#endif  //_WIN64
    ) {
      Zydis cp;
      unsigned char data[MAX_DISASM_BUFFER];
      memset(data, 0, sizeof(data));
      MemRead(cip, data, MAX_DISASM_BUFFER);
      if (cp.Disassemble(cip, data) && cp.IsRet())
        cbRtrFinalStep(true);
      else
        StepOverWrapper((void*)cbRtrStep);
    } else {
      StepOverWrapper((void*)cbRtrStep);
    }
  } else
    StepOverWrapper((void*)cbRtrStep);
}

static void cbTraceUniversalConditionalStep(duint cip, bool bStepInto,
                                            void (*callback)(),
                                            bool forceBreakTrace) {
  PLUG_CB_TRACEEXECUTE info;
  info.cip = cip;
  auto breakCondition =
      (info.stop = traceState.BreakTrace() || forceBreakTrace);
  if (traceState.IsExtended())  // only set when needed
    varset("$tracecounter", traceState.StepCount(), true);
  plugincbcall(CB_TRACEEXECUTE, &info);
  breakCondition = info.stop;
  auto logCondition = traceState.EvaluateLog(true);
  auto cmdCondition = traceState.EvaluateCmd(breakCondition);
  auto switchCondition = traceState.EvaluateSwitch(false);
  if (logCondition)  // log
  {
    traceState.LogWrite(stringformatinline(traceState.LogText()));
  }
  if (cmdCondition)  // command
  {
    // TODO: commands like run/step etc will fuck up your shit
    varset("$tracecondition", breakCondition ? 1 : 0, false);
    varset("$tracelogcondition", logCondition ? 1 : 0, true);
    varset("$traceswitchcondition", switchCondition ? 1 : 0, false);
    cmddirectexec(traceState.CmdText().c_str());
    duint script_breakcondition;
    if (varget("$tracecondition", &script_breakcondition, nullptr, nullptr))
      breakCondition = script_breakcondition != 0;
    if (varget("$traceswitchcondition", &script_breakcondition, nullptr,
               nullptr))
      switchCondition = script_breakcondition != 0;
  }
  if (breakCondition || traceState.ForceBreakTrace())  // break the debugger
  {
    auto steps = dbgcleartracestate();
    varset("$tracecounter", steps, true);
#ifdef _WIN64
    dprintf(QT_TRANSLATE_NOOP("DBG", "Trace finished after %llu steps!\n"),
            steps);
#else  // x86
    dprintf(QT_TRANSLATE_NOOP("DBG", "Trace finished after %u steps!\n"),
            steps);
#endif  //_WIN64
    cbRtrFinalStep();
  } else  // continue tracing
  {
    if (bTraceRecordEnabledDuringTrace) _dbg_dbgtraceexecute(cip);
    if (switchCondition)  // switch (invert) the step type once
      bStepInto = !bStepInto;
    (bStepInto ? StepIntoWow64 : StepOverWrapper)((void*)callback);
  }
}

static void cbTraceXConditionalStep(bool bStepInto, void (*callback)()) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  cbTraceUniversalConditionalStep(GetContextDataEx(hActiveThread, UE_CIP),
                                  bStepInto, callback, false);
}

static void cbTraceXXTraceRecordStep(bool bStepInto, bool bInto,
                                     void (*callback)()) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  auto cip = GetContextDataEx(hActiveThread, UE_CIP);
  auto forceBreakTrace = TraceRecord.getTraceRecordType(cip) !=
                             TraceRecordManager::TraceRecordNone &&
                         (TraceRecord.getHitCount(cip) == 0) ^ bInto;
  cbTraceUniversalConditionalStep(cip, bStepInto, callback, forceBreakTrace);
}

void cbTraceOverConditionalStep() {
  cbTraceXConditionalStep(false, cbTraceOverConditionalStep);
}

void cbTraceIntoConditionalStep() {
  cbTraceXConditionalStep(true, cbTraceIntoConditionalStep);
}

void cbTraceIntoBeyondTraceRecordStep() {
  cbTraceXXTraceRecordStep(true, false, cbTraceIntoBeyondTraceRecordStep);
}

void cbTraceOverBeyondTraceRecordStep() {
  cbTraceXXTraceRecordStep(false, false, cbTraceOverBeyondTraceRecordStep);
}

void cbTraceIntoIntoTraceRecordStep() {
  cbTraceXXTraceRecordStep(true, true, cbTraceIntoIntoTraceRecordStep);
}

void cbTraceOverIntoTraceRecordStep() {
  cbTraceXXTraceRecordStep(false, true, cbTraceOverIntoTraceRecordStep);
}

static void cbCreateProcess(CREATE_PROCESS_DEBUG_INFO* CreateProcessInfo) {
  fdProcessInfo->hProcess = CreateProcessInfo->hProcess;
  fdProcessInfo->hThread = CreateProcessInfo->hThread;
  varset("$hp", (duint)fdProcessInfo->hProcess, true);

  void* base = CreateProcessInfo->lpBaseOfImage;

  char DebugFileName[deflen] = "";
  if (!GetFileNameFromHandle(CreateProcessInfo->hFile, DebugFileName) &&
      !GetFileNameFromProcessHandle(CreateProcessInfo->hProcess, DebugFileName))
    strcpy_s(DebugFileName, GuiTranslateText(QT_TRANSLATE_NOOP(
                                "DBG", "??? (GetFileNameFromHandle failed)")));
  dprintf(QT_TRANSLATE_NOOP("DBG", "Process Started: %p %s\n"), base,
          DebugFileName);

  char* cmdline = nullptr;
  if (dbggetcmdline(&cmdline, nullptr, fdProcessInfo->hProcess)) {
    // Parse the command line from the debuggee
    int argc = 0;
    wchar_t** argv =
        CommandLineToArgvW(StringUtils::Utf8ToUtf16(cmdline).c_str(), &argc);

    // Print the command line to the log
    dprintf_untranslated("  %s\n", cmdline);
    for (int i = 0; i < argc; i++)
      dprintf_untranslated("  argv[%i]: %s\n", i,
                           StringUtils::Utf16ToUtf8(argv[i]).c_str());

    LocalFree(argv);
    efree(cmdline);
  }

  // update memory map
  MemUpdateMap();
  GuiUpdateMemoryView();

  GuiDumpAt(
      MemFindBaseAddr(GetContextDataEx(CreateProcessInfo->hThread, UE_CIP), 0) +
      PAGE_SIZE);  // dump somewhere

  ModLoad((duint)base, 1, DebugFileName);

  char modname[256] = "";
  if (ModNameFromAddr((duint)base, modname, true))
    BpEnumAll(cbSetModuleBreakpoints, modname, duint(base));
  BpEnumAll(cbSetDLLBreakpoints);
  BpEnumAll(cbSetModuleBreakpoints, "");
  DebugUpdateBreakpointsViewAsync();
  pCreateProcessBase = (duint)CreateProcessInfo->lpBaseOfImage;
  pDebuggedBase = pCreateProcessBase;  // debugged base = executable
  DbCheckHash(ModContentHashFromAddr(pDebuggedBase));  // Check hash mismatch
  if (!bFileIsDll && !bIsAttached)                     // Set entry breakpoint
  {
    char command[deflen] = "";

    if (settingboolget("Events", "TlsCallbacks")) {
      SHARED_ACQUIRE(LockModules);
      auto modInfo = ModInfoFromAddr(duint(base));
      int invalidCount = 0;
      for (size_t i = 0; i < modInfo->tlsCallbacks.size(); i++) {
        auto callbackVA = modInfo->tlsCallbacks.at(i);
        if (MemIsValidReadPtr(callbackVA)) {
          String breakpointname = StringUtils::sprintf(
              GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "TLS Callback %d")),
              i + 1);
          sprintf_s(command, "bp %p,\"%s\",ss", callbackVA,
                    breakpointname.c_str());
          cmddirectexec(command);
        } else
          invalidCount++;
      }
      if (invalidCount)
        dprintf(
            QT_TRANSLATE_NOOP("DBG", "%d invalid TLS callback addresses...\n"),
            invalidCount);
    }

    if (settingboolget("Events", "EntryBreakpoint") && !bEntryIsInMzHeader) {
      sprintf_s(command, "bp %p,\"%s\",ss", pDebuggedBase + pDebuggedEntry,
                GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "entry breakpoint")));
      cmddirectexec(command);
    }

    bTraceRecordEnabledDuringTrace =
        settingboolget("Engine", "TraceRecordEnabledDuringTrace");
  } else if (bFileIsDll &&
             strstr(DebugFileName,
                    "DLLLoader" ArchValue("32", "64")))  // DLL Loader
    gDllLoader = StringUtils::Utf8ToUtf16(DebugFileName);

  DebugUpdateBreakpointsViewAsync();

  // call plugin callback
  PLUG_CB_CREATEPROCESS callbackInfo;
  callbackInfo.CreateProcessInfo = CreateProcessInfo;
  IMAGEHLP_MODULE64 modInfoUtf8;
  memset(&modInfoUtf8, 0, sizeof(modInfoUtf8));
  modInfoUtf8.SizeOfStruct = sizeof(modInfoUtf8);
  modInfoUtf8.BaseOfImage = (DWORD64)base;
  modInfoUtf8.ImageSize = 0;
  modInfoUtf8.TimeDateStamp = 0;
  modInfoUtf8.CheckSum = 0;
  modInfoUtf8.NumSyms = 1;
  modInfoUtf8.SymType = SymDia;
  strncpy_s(modInfoUtf8.ModuleName, DebugFileName, _TRUNCATE);
  strncpy_s(modInfoUtf8.ImageName, DebugFileName, _TRUNCATE);
  strncpy_s(modInfoUtf8.LoadedImageName, "", _TRUNCATE);
  strncpy_s(modInfoUtf8.LoadedPdbName, "", _TRUNCATE);

  modInfoUtf8.CVSig = 0;
  strncpy_s(modInfoUtf8.CVData, "", _TRUNCATE);
  modInfoUtf8.PdbSig = 0;
  modInfoUtf8.PdbAge = 0;
  modInfoUtf8.PdbUnmatched = FALSE;
  modInfoUtf8.DbgUnmatched = FALSE;
  modInfoUtf8.LineNumbers = TRUE;
  modInfoUtf8.GlobalSymbols = 0;
  modInfoUtf8.TypeInfo = TRUE;
  modInfoUtf8.SourceIndexed = TRUE;
  modInfoUtf8.Publics = TRUE;

  callbackInfo.modInfo = &modInfoUtf8;
  callbackInfo.DebugFileName = DebugFileName;
  callbackInfo.fdProcessInfo = fdProcessInfo;
  plugincbcall(CB_CREATEPROCESS, &callbackInfo);

  // update thread list
  CREATE_THREAD_DEBUG_INFO threadInfo;
  threadInfo.hThread = CreateProcessInfo->hThread;
  threadInfo.lpStartAddress = CreateProcessInfo->lpStartAddress;
  threadInfo.lpThreadLocalBase = CreateProcessInfo->lpThreadLocalBase;
  ThreadCreate(&threadInfo);
}

static void cbExitProcess(EXIT_PROCESS_DEBUG_INFO* ExitProcess) {
  dprintf(QT_TRANSLATE_NOOP("DBG", "Process stopped with exit code 0x%X\n"),
          ExitProcess->dwExitCode);
  PLUG_CB_EXITPROCESS callbackInfo;
  callbackInfo.ExitProcess = ExitProcess;
  plugincbcall(CB_EXITPROCESS, &callbackInfo);
  _dbg_animatestop();  // Stop animating
  // history
  dbgcleartracestate();
  dbgClearRtuBreakpoints();
  HistoryClear();
}

static void cbCreateThread(CREATE_THREAD_DEBUG_INFO* CreateThread) {
  ThreadCreate(CreateThread);  // update thread list
  DWORD dwThreadId = ((DEBUG_EVENT*)GetDebugData())->dwThreadId;
  hActiveThread = ThreadGetHandle(dwThreadId);

  PLUG_CB_CREATETHREAD callbackInfo;
  callbackInfo.CreateThread = CreateThread;
  callbackInfo.dwThreadId = dwThreadId;
  plugincbcall(CB_CREATETHREAD, &callbackInfo);

  auto entry = duint(CreateThread->lpStartAddress);
  auto symbolic = SymGetSymbolicName(entry);
  if (!symbolic.length()) symbolic = StringUtils::sprintf("%p", entry);
  dprintf(QT_TRANSLATE_NOOP("DBG", "Thread %X created, Entry: %s\n"),
          dwThreadId, symbolic.c_str());

  if (settingboolget("Events", "ThreadEntry")) {
    String command;
    command = StringUtils::sprintf(
        "bp %p,\"%s %X\",ss", entry,
        GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Thread Entry")), dwThreadId);
    cmddirectexec(command.c_str());
  }

  if (settingboolget("Events", "ThreadStart")) {
    HistoryClear();
    // update memory map
    MemUpdateMap();
    // update GUI
    DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
    // lock
    lock(WAITID_RUN);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    dbgsetskipexceptions(false);
    wait(WAITID_RUN);
  } else {
    // insert the thread stack as a dummy page to prevent cache misses (issue
    // #1475)
    NT_TIB tib;
    if (ThreadGetTib(ThreadGetLocalBase(dwThreadId), &tib)) {
      MEMPAGE page;
      auto limit = duint(tib.StackLimit);
      auto base = duint(tib.StackBase);
      sprintf_s(page.info,
                GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Thread %X Stack")),
                dwThreadId);
      page.mbi.BaseAddress = page.mbi.AllocationBase = tib.StackLimit;
      page.mbi.Protect = page.mbi.AllocationProtect = PAGE_READWRITE;
      page.mbi.RegionSize = base - limit;
      page.mbi.State = MEM_COMMIT;
      page.mbi.Type = MEM_PRIVATE;

      EXCLUSIVE_ACQUIRE(LockMemoryPages);
      memoryPages.insert({Range(limit, base - 1), page});
    }
  }
}

static void cbExitThread(EXIT_THREAD_DEBUG_INFO* ExitThread) {
  // Not called when the main (last) thread exits. Instead
  // EXIT_PROCESS_DEBUG_EVENT is signalled.
  // Switch to the main thread (because the thread is terminated).
  hActiveThread = ThreadGetHandle(fdProcessInfo->dwThreadId);
  if (!hActiveThread) {
    std::vector<THREADINFO> threads;
    ThreadGetList(threads);
    if (threads.size())
      hActiveThread = threads[0].Handle;
    else
      dputs(QT_TRANSLATE_NOOP("DBG", "No threads left to switch to (bug?)"));
  }
  DWORD dwThreadId = ((DEBUG_EVENT*)GetDebugData())->dwThreadId;
  PLUG_CB_EXITTHREAD callbackInfo;
  callbackInfo.ExitThread = ExitThread;
  callbackInfo.dwThreadId = dwThreadId;
  plugincbcall(CB_EXITTHREAD, &callbackInfo);
  HistoryClear();
  ThreadExit(dwThreadId);
  dprintf(QT_TRANSLATE_NOOP("DBG", "Thread %X exit\n"), dwThreadId);

  if (settingboolget("Events", "ThreadEnd")) {
    // update GUI
    DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
    // lock
    lock(WAITID_RUN);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    dbgsetskipexceptions(false);
    wait(WAITID_RUN);
  }
}

static DWORD WINAPI cbInitializationScriptThread(void*) {
  Memory<char*> script(MAX_SETTING_SIZE + 1);
  if (BridgeSettingGet("Engine", "InitializeScript",
                       script()))  // Global script file
  {
    if (scriptLoadSync(script())) {
      if (scriptRunSync(0, true)) scriptunload();
    } else
      dputs(QT_TRANSLATE_NOOP(
          "DBG", "Error: Cannot load global initialization script."));
  }
  if (szDebuggeeInitializationScript[0] != 0) {
    if (scriptLoadSync(szDebuggeeInitializationScript)) {
      if (scriptRunSync(0, true)) scriptunload();
    } else
      dputs(QT_TRANSLATE_NOOP(
          "DBG", "Error: Cannot load debuggee initialization script."));
  }
  return 0;
}

static void cbSystemBreakpoint(
    void* ExceptionData)  // TODO: System breakpoint event shouldn't be dropped
{
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);

  // Get on top of things
  SetForegroundWindow(GuiGetWindowHandle());

  // Update GUI (this should be the first triggered event)
  duint cip = GetContextDataEx(hActiveThread, UE_CIP);
  GuiDumpAt(MemFindBaseAddr(cip, 0, true));  // dump somewhere
  DebugUpdateGuiSetStateAsync(cip, true, running);

  MemInitRemoteProcessCookie(cookie.cookie);
  GuiUpdateAllViews();

  // log message
  if (bIsAttached)
    dputs(QT_TRANSLATE_NOOP("DBG", "Attach breakpoint reached!"));
  else
    dputs(QT_TRANSLATE_NOOP("DBG", "System breakpoint reached!"));
  dbgsetskipexceptions(false);  // we are not skipping first-chance exceptions

  // plugin callbacks
  PLUG_CB_SYSTEMBREAKPOINT callbackInfo;
  callbackInfo.reserved = 0;
  plugincbcall(CB_SYSTEMBREAKPOINT, &callbackInfo);

  lock(WAITID_RUN);  // Allow the user to run a script file now
  bool systemBreakpoint = settingboolget("Events", "SystemBreakpoint");
  if (!systemBreakpoint && bEntryIsInMzHeader) {
    dputs(QT_TRANSLATE_NOOP(
        "DBG",
        "It has been detected that the debuggee entry point is in the MZ "
        "header of the executable. This will cause strange behavior, so the "
        "system breakpoint has been enabled regardless of your setting. Be "
        "careful!"));
    systemBreakpoint = true;
  }
  if (bIsAttached ? settingboolget("Events", "AttachBreakpoint")
                  : systemBreakpoint) {
    // lock
    GuiSetDebugStateAsync(paused);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    CloseHandle(
        CreateThread(NULL, 0, cbInitializationScriptThread, NULL, 0, NULL));
  } else {
    CloseHandle(
        CreateThread(NULL, 0, cbInitializationScriptThread, NULL, 0, NULL));
    unlock(WAITID_RUN);
  }
  wait(WAITID_RUN);
}

static void cbLoadDll(LOAD_DLL_DEBUG_INFO* LoadDll) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  void* base = LoadDll->lpBaseOfDll;

  char DLLDebugFileName[deflen] = "";
  if (!GetFileNameFromHandle(LoadDll->hFile, DLLDebugFileName) &&
      !GetFileNameFromModuleHandle(fdProcessInfo->hProcess, HMODULE(base),
                                   DLLDebugFileName))
    strcpy_s(DLLDebugFileName,
             GuiTranslateText(QT_TRANSLATE_NOOP(
                 "DBG", "??? (GetFileNameFromHandle failed)")));

  ModLoad((duint)base, 1, DLLDebugFileName);

  // Update memory map
  MemUpdateMapAsync();

  char modname[MAX_MODULE_SIZE] = "";
  if (ModNameFromAddr(duint(base), modname, true))
    BpEnumAll(cbSetModuleBreakpoints, modname, duint(base));
  DebugUpdateBreakpointsViewAsync();
  bool bAlreadySetEntry = false;

  char command[MAX_PATH * 2] = "";
  bool bIsDebuggingThis = false;
  if (bFileIsDll && !_stricmp(DLLDebugFileName, szDebuggeePath) &&
      !bIsAttached)  // Set entry breakpoint
  {
    CloseHandle(DebugDLLFileMapping);
    DebugDLLFileMapping = 0;
    bIsDebuggingThis = true;
    pDebuggedBase = (duint)base;
    DbCheckHash(ModContentHashFromAddr(pDebuggedBase));  // Check hash mismatch
    if (settingboolget("Events", "EntryBreakpoint")) {
      bAlreadySetEntry = true;
      sprintf_s(command, "bp %p,\"%s\",ss", pDebuggedBase + pDebuggedEntry,
                GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "entry breakpoint")));
      cmddirectexec(command);
    }
  }
  DebugUpdateBreakpointsViewAsync();

  int party = ModGetParty(duint(base));

  if (settingboolget("Events", "TlsCallbacks") && party != mod_system ||
      settingboolget("Events", "TlsCallbacksSystem") && party == mod_system) {
    SHARED_ACQUIRE(LockModules);
    auto modInfo = ModInfoFromAddr(duint(base));
    int invalidCount = 0;
    for (size_t i = 0; i < modInfo->tlsCallbacks.size(); i++) {
      auto callbackVA = modInfo->tlsCallbacks.at(i);
      if (MemIsValidReadPtr(callbackVA)) {
        if (bIsDebuggingThis)
          sprintf_s(command, "bp %p,\"%s %u\",ss", callbackVA,
                    GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "TLS Callback")),
                    i + 1);
        else
          sprintf_s(command, "bp %p,\"%s %u (%s)\",ss", callbackVA,
                    GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "TLS Callback")),
                    i + 1, modname);
        cmddirectexec(command);
      } else
        invalidCount++;
    }
    if (invalidCount)
      dprintf(
          QT_TRANSLATE_NOOP("DBG", "%d invalid TLS callback addresses...\n"),
          invalidCount);
  }

  auto breakOnDll = dbghandledllbreakpoint(modname, true);

  if ((breakOnDll ||
       (settingboolget("Events", "DllEntry") && party != mod_system ||
        settingboolget("Events", "DllEntrySystem") && party == mod_system)) &&
      !bAlreadySetEntry) {
    auto entry = ModEntryFromAddr(duint(base));
    if (entry) {
      sprintf_s(command, "bp %p,\"DllMain (%s)\",ss", entry, modname);
      cmddirectexec(command);
    }
  }

  if (ModNameFromAddr(duint(base), modname, true) &&
      scmp(modname, "ntdll.dll")) {
    if (settingboolget("Misc", "QueryProcessCookie"))
      cookie.HandleNtdllLoad(bIsAttached);
    if (settingboolget("Misc", "TransparentExceptionStepping"))
      exceptionDispatchAddr =
          DbgValFromString("ntdll:KiUserExceptionDispatcher");
    if (settingboolget("Events",
                       "NtTerminateProcess"))  // Break on NtTerminateProcess
      cmddirectexec("bp ntdll.NtTerminateProcess, ss");
    // set debug flags
    if (dwDebugFlags != 0) {
      SHARED_ACQUIRE(LockModules);
      auto info = ModInfoFromAddr(duint(base));
      if (info->symbols->isOpen()) {
        dprintf(QT_TRANSLATE_NOOP(
            "DBG", "Waiting until ntdll.dll symbols are loaded...\n"));
        info->symbols->waitUntilLoaded();
        SymbolInfo LdrpDebugFlags;
        if (info->symbols->findSymbolByName("LdrpDebugFlags", LdrpDebugFlags,
                                            true)) {
          if (MemWrite(info->base + LdrpDebugFlags.rva, &dwDebugFlags,
                       sizeof(dwDebugFlags)))
            dprintf(QT_TRANSLATE_NOOP(
                        "DBG", "Set LdrpDebugFlags to 0x%08X successfully!\n"),
                    dwDebugFlags);
          else
            dprintf(QT_TRANSLATE_NOOP("DBG",
                                      "Failed to write to LdrpDebugFlags\n"));
        } else {
          dprintf(
              QT_TRANSLATE_NOOP("DBG", "Symbol 'LdrpDebugFlags' not found!\n"));
        }
      } else {
        dprintf(QT_TRANSLATE_NOOP("DBG",
                                  "Failed to find LdrpDebugFlags (you need to "
                                  "load symbols for ntdll.dll)\n"));
      }
    }
  }

  dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Loaded: %p %s\n"), base,
          DLLDebugFileName);

  // plugin callback
  PLUG_CB_LOADDLL callbackInfo;
  callbackInfo.LoadDll = LoadDll;
  IMAGEHLP_MODULE64 modInfoUtf8;
  memset(&modInfoUtf8, 0, sizeof(modInfoUtf8));
  modInfoUtf8.SizeOfStruct = sizeof(modInfoUtf8);
  modInfoUtf8.BaseOfImage = (DWORD64)base;
  modInfoUtf8.ImageSize = 0;
  modInfoUtf8.TimeDateStamp = 0;
  modInfoUtf8.CheckSum = 0;
  modInfoUtf8.NumSyms = 0;
  modInfoUtf8.SymType = SymDia;
  strncpy_s(modInfoUtf8.ModuleName, DLLDebugFileName, _TRUNCATE);
  strncpy_s(modInfoUtf8.ImageName, DLLDebugFileName, _TRUNCATE);
  strncpy_s(modInfoUtf8.LoadedImageName, "", _TRUNCATE);
  strncpy_s(modInfoUtf8.LoadedPdbName, "", _TRUNCATE);
  modInfoUtf8.CVSig = 0;
  strncpy_s(modInfoUtf8.CVData, "", _TRUNCATE);
  modInfoUtf8.PdbSig = 0;
  modInfoUtf8.PdbAge = 0;
  modInfoUtf8.PdbUnmatched = FALSE;
  modInfoUtf8.DbgUnmatched = FALSE;
  modInfoUtf8.LineNumbers = 0;
  modInfoUtf8.GlobalSymbols = TRUE;
  modInfoUtf8.TypeInfo = TRUE;
  modInfoUtf8.SourceIndexed = TRUE;
  modInfoUtf8.Publics = TRUE;
  callbackInfo.modInfo = &modInfoUtf8;
  callbackInfo.modname = modname;
  plugincbcall(CB_LOADDLL, &callbackInfo);

  if (breakOnDll) {
    cbGenericBreakpoint(BPDLL, DLLDebugFileName);
  } else if (settingboolget("Events", "DllLoad") && party != mod_system ||
             settingboolget("Events", "DllLoadSystem") && party == mod_system) {
    // update GUI
    DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
    // lock
    lock(WAITID_RUN);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    dbgsetskipexceptions(false);
    wait(WAITID_RUN);
  }
}

static void cbUnloadDll(UNLOAD_DLL_DEBUG_INFO* UnloadDll) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  PLUG_CB_UNLOADDLL callbackInfo;
  callbackInfo.UnloadDll = UnloadDll;
  plugincbcall(CB_UNLOADDLL, &callbackInfo);

  void* base = UnloadDll->lpBaseOfDll;
  char modname[256] = "???";
  if (ModNameFromAddr((duint)base, modname, true))
    BpEnumAll(cbRemoveModuleBreakpoints, modname, duint(base));
  int party = ModGetParty(duint(base));
  DebugUpdateBreakpointsViewAsync();
  dprintf(QT_TRANSLATE_NOOP("DBG", "DLL Unloaded: %p %s\n"), base, modname);

  if (dbghandledllbreakpoint(modname, false)) {
    cbGenericBreakpoint(BPDLL, modname);
  } else if (settingboolget("Events", "DllUnload") && party != mod_system ||
             settingboolget("Events", "DllUnloadSystem") &&
                 party == mod_system) {
    // update GUI
    DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
    // lock
    lock(WAITID_RUN);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    dbgsetskipexceptions(false);
    wait(WAITID_RUN);
  }

  ModUnload((duint)base);

  // update memory map
  MemUpdateMapAsync();
}

static void cbOutputDebugString(OUTPUT_DEBUG_STRING_INFO* DebugString) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  PLUG_CB_OUTPUTDEBUGSTRING callbackInfo;
  callbackInfo.DebugString = DebugString;
  plugincbcall(CB_OUTPUTDEBUGSTRING, &callbackInfo);

  if (!DebugString->fUnicode)  // ASCII
  {
    Memory<char*> DebugText(DebugString->nDebugStringLength + 1,
                            "cbOutputDebugString:DebugText");
    if (MemRead((duint)DebugString->lpDebugStringData, DebugText(),
                DebugString->nDebugStringLength)) {
      String str = String(DebugText());
      if (str != lastDebugText)  // fix for every string being printed twice
      {
        if (str != "\n")
          dprintf(QT_TRANSLATE_NOOP("DBG", "DebugString: \"%s\"\n"),
                  StringUtils::Trim(StringUtils::Escape(str, false)).c_str());
        lastDebugText = str;
      } else
        lastDebugText.clear();
    }
  } else {
    // TODO: implement Windows 10 unicode debug string
  }

  if (settingboolget("Events", "DebugStrings")) {
    // update GUI
    DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
    // lock
    lock(WAITID_RUN);
    // Plugin callback
    PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
    plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
    dbgsetforeground();
    dbgsetskipexceptions(false);
    wait(WAITID_RUN);
  }
}

static bool dbgdetachDisableAllBreakpoints(const BREAKPOINT* bp) {
  if (bp->enabled) {
    if (bp->type == BPNORMAL)
      DeleteBPX(bp->addr);
    else if (bp->type == BPMEMORY)
      RemoveMemoryBPX(bp->addr, 0);
    else if (bp->type == BPHARDWARE && TITANDRXVALID(bp->titantype))
      DeleteHardwareBreakPoint(TITANGETDRX(bp->titantype));
  }
  return true;
}

static void cbException(EXCEPTION_DEBUG_INFO* ExceptionData) {
  hActiveThread = ThreadGetHandle(((DEBUG_EVENT*)GetDebugData())->dwThreadId);
  PLUG_CB_EXCEPTION callbackInfo;
  callbackInfo.Exception = ExceptionData;
  unsigned int ExceptionCode = ExceptionData->ExceptionRecord.ExceptionCode;
  GuiSetLastException(ExceptionCode);
  lastExceptionInfo = *ExceptionData;

  duint addr = (duint)ExceptionData->ExceptionRecord.ExceptionAddress;
  {
    BREAKPOINT bp;
    if (BpGet(ExceptionCode, BPEXCEPTION, nullptr, &bp) && bp.enabled &&
        ((bp.titantype == 1 && ExceptionData->dwFirstChance) ||
         (bp.titantype == 2 && !ExceptionData->dwFirstChance) ||
         bp.titantype == 3)) {
      bPausedOnException = true;
      cbGenericBreakpoint(BPEXCEPTION, ExceptionData);
      bPausedOnException = false;
      return;
    }
  }
  if (ExceptionData->ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) {
    if (isDetachedByUser) {
      PLUG_CB_DETACH detachInfo;
      detachInfo.fdProcessInfo = fdProcessInfo;
      plugincbcall(CB_DETACH, &detachInfo);
      BpEnumAll(
          dbgdetachDisableAllBreakpoints);  // Disable all software breakpoints
                                            // before detaching.
      if (!DetachDebuggerEx(fdProcessInfo->dwProcessId))
        dputs(QT_TRANSLATE_NOOP("DBG", "DetachDebuggerEx failed..."));
      else
        dputs(QT_TRANSLATE_NOOP("DBG", "Detached!"));
      isDetachedByUser = false;
      _dbg_animatestop();  // Stop animating
      return;
    }
  } else if (ExceptionData->ExceptionRecord.ExceptionCode ==
             MS_VC_EXCEPTION)  // SetThreadName exception
  {
    THREADNAME_INFO nameInfo;  // has no valid local pointers
    memcpy(&nameInfo, ExceptionData->ExceptionRecord.ExceptionInformation,
           sizeof(THREADNAME_INFO));
    if (nameInfo.dwThreadID == -1)  // current thread
      nameInfo.dwThreadID = ((DEBUG_EVENT*)GetDebugData())->dwThreadId;
    if (nameInfo.dwType == 0x1000 && nameInfo.dwFlags == 0 &&
        ThreadIsValid(nameInfo.dwThreadID))  // passed basic checks
    {
      Memory<char*> ThreadName(MAX_THREAD_NAME_SIZE, "cbException:ThreadName");
      if (MemRead((duint)nameInfo.szName, ThreadName(),
                  MAX_THREAD_NAME_SIZE - 1)) {
        String ThreadNameEscaped = StringUtils::Escape(ThreadName());
        dprintf(QT_TRANSLATE_NOOP("DBG", "SetThreadName(%X, \"%s\")\n"),
                nameInfo.dwThreadID, ThreadNameEscaped.c_str());
        ThreadSetName(nameInfo.dwThreadID, ThreadNameEscaped.c_str());
      }
    }
  }
  if (bVerboseExceptionLogging)
    DbgCmdExecDirect("exinfo");  // show extended exception information
  auto exceptionName = ExceptionCodeToName(ExceptionCode);
  if (!exceptionName
           .size())  // if no exception was found, try the error codes (RPC_S_*)
    exceptionName = ErrorCodeToName(ExceptionCode);
  if (ExceptionData->dwFirstChance)  // first chance exception
  {
    if (exceptionName.size())
      dprintf(QT_TRANSLATE_NOOP("DBG",
                                "First chance exception on %p (%.8X, %s)!\n"),
              addr, ExceptionCode, exceptionName.c_str());
    else
      dprintf(
          QT_TRANSLATE_NOOP("DBG", "First chance exception on %p (%.8X)!\n"),
          addr, ExceptionCode);
    SetNextDbgContinueStatus(DBG_EXCEPTION_NOT_HANDLED);
    if ((bSkipExceptions || dbgisignoredexception(ExceptionCode)) &&
        (!maxSkipExceptionCount ||
         ++skipExceptionCount < maxSkipExceptionCount))
      return;
  } else  // lock the exception
  {
    if (exceptionName.size())
      dprintf(
          QT_TRANSLATE_NOOP("DBG", "Last chance exception on %p (%.8X, %s)!\n"),
          addr, ExceptionCode, exceptionName.c_str());
    else
      dprintf(QT_TRANSLATE_NOOP("DBG", "Last chance exception on %p (%.8X)!\n"),
              addr, ExceptionCode);
    SetNextDbgContinueStatus(DBG_CONTINUE);
  }

  DebugUpdateGuiSetStateAsync(GetContextDataEx(hActiveThread, UE_CIP), true);
  // lock
  lock(WAITID_RUN);
  bPausedOnException = true;
  // Plugin callback
  PLUG_CB_PAUSEDEBUG pauseInfo = {nullptr};
  plugincbcall(CB_PAUSEDEBUG, &pauseInfo);
  dbgsetforeground();
  dbgsetskipexceptions(false);
  plugincbcall(CB_EXCEPTION, &callbackInfo);
  wait(WAITID_RUN);
  bPausedOnException = false;
}

static void cbDebugEvent(DEBUG_EVENT* DebugEvent) {
  InterlockedIncrement((volatile long*)&DbgEvents);
  PLUG_CB_DEBUGEVENT debugEventInfo;
  debugEventInfo.DebugEvent = DebugEvent;
  plugincbcall(CB_DEBUGEVENT, &debugEventInfo);
}

static void cbAttachDebugger() {
  if (hEvent)  // Signal the AeDebug event
  {
    SetEvent(hEvent);
    CloseHandle(hEvent);
    hEvent = 0;
  }
  if (tidToResume)  // Resume a thread
  {
    cmddirectexec(StringUtils::sprintf("resumethread %p", tidToResume).c_str());
    tidToResume = 0;
  }
  varset("$pid", fdProcessInfo->dwProcessId, true);
}

void cbDetach() {
  if (!isDetachedByUser) return;
  PLUG_CB_DETACH detachInfo;
  detachInfo.fdProcessInfo = fdProcessInfo;
  plugincbcall(CB_DETACH, &detachInfo);
  BpEnumAll(dbgdetachDisableAllBreakpoints);  // Disable all software
                                              // breakpoints before detaching.
  if (!DetachDebuggerEx(fdProcessInfo->dwProcessId))
    dputs(QT_TRANSLATE_NOOP("DBG", "DetachDebuggerEx failed..."));
  else
    dputs(QT_TRANSLATE_NOOP("DBG", "Detached!"));
  return;
}

cmdline_qoutes_placement_t getqoutesplacement(const char* cmdline) {
  cmdline_qoutes_placement_t quotesPos;
  quotesPos.firstPos = quotesPos.secondPos = 0;

  auto len = strlen(cmdline);

  char quoteSymb = cmdline[0];
  if (quoteSymb == '"' || quoteSymb == '\'') {
    for (size_t i = 1; i < len; i++) {
      if (cmdline[i] == quoteSymb) {
        quotesPos.posEnum =
            i == len - 1 ? QOUTES_AT_BEGIN_AND_END : QOUTES_AROUND_EXE;
        quotesPos.secondPos = i;
        break;
      }
    }
    if (!quotesPos.secondPos) quotesPos.posEnum = NO_CLOSE_QUOTE_FOUND;
  } else {
    quotesPos.posEnum = NO_QOUTES;
    // try to locate first quote
    for (size_t i = 1; i < len; i++)
      if (cmdline[i] == '"' || cmdline[i] == '\'') quotesPos.secondPos = i;
  }

  return quotesPos;
}

BOOL ismainwindow(HWND handle) {
  // using only OWNER condition allows getting titles of hidden "main windows"
  return !GetWindow(handle, GW_OWNER) && IsWindowVisible(handle);
}

BOOL CALLBACK chkWindowPidCallback(HWND hWnd, LPARAM lParam) {
  DWORD procId = (DWORD)lParam;
  DWORD hwndPid = 0;
  GetWindowThreadProcessId(hWnd, &hwndPid);
  if (hwndPid == procId) {
    if (!mForegroundHandle)  // get the foreground if no owner visible
      mForegroundHandle = hWnd;

    if (ismainwindow(hWnd)) {
      mProcHandle = hWnd;
      return FALSE;
    }
  }

  return TRUE;
}

bool dbggetwintext(std::vector<std::string>* winTextList,
                   const DWORD dwProcessId) {
  mProcHandle = NULL;
  mForegroundHandle = NULL;

  EnumWindows(chkWindowPidCallback, dwProcessId);
  if (!mProcHandle && !mForegroundHandle) return false;

  wchar_t limitedbuffer[256];
  limitedbuffer[255] = 0;

  if (mProcHandle)  // get info from the "main window" (GW_OWNER + visible)
  {
    if (!GetWindowTextW((HWND)mProcHandle, limitedbuffer, 256))
      GetClassNameW((HWND)mProcHandle, limitedbuffer,
                    256);        // go for the class name if none of the above
  } else if (mForegroundHandle)  // get info from the foreground window
  {
    if (!GetWindowTextW((HWND)mForegroundHandle, limitedbuffer, 256))
      GetClassNameW((HWND)mForegroundHandle, limitedbuffer,
                    256);  // go for the class name if none of the above
  }

  if (limitedbuffer[255] !=
      0)  // Window title too long. Add "..." to the end of buffer.
  {
    if (limitedbuffer[252] < 0xDC00 ||
        limitedbuffer[252] >
            0xDFFF)  // protect the last surrogate of UTF-16 surrogate pair
      limitedbuffer[252] = L'.';
    limitedbuffer[253] = L'.';
    limitedbuffer[254] = L'.';
    limitedbuffer[255] = 0;
  }
  auto UTF8WindowTitle = StringUtils::Utf16ToUtf8(limitedbuffer);
  winTextList->push_back(UTF8WindowTitle);
  return true;
}

bool dbglistprocesses(std::vector<PROCESSENTRY32>* infoList,
                      std::vector<std::string>* commandList,
                      std::vector<std::string>* winTextList) {
  infoList->clear();
  Handle hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  if (!hProcessSnap) return false;
  PROCESSENTRY32 pe32;
  pe32.dwSize = sizeof(PROCESSENTRY32);
  if (!Process32First(hProcessSnap, &pe32)) return false;
  do {
    if (pe32.th32ProcessID == GetCurrentProcessId()) continue;
    if (pe32.th32ProcessID == 0 ||
        pe32.th32ProcessID ==
            4)  // System process and Idle process have special PID.
      continue;
    Handle hProcess = TitanOpenProcess(
        PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pe32.th32ProcessID);
    if (!hProcess) continue;
    BOOL wow64 = false, mewow64 = false;
    if (!IsWow64Process(hProcess, &wow64) ||
        !IsWow64Process(GetCurrentProcess(), &mewow64))
      continue;
    if ((mewow64 && !wow64) || (!mewow64 && wow64)) continue;
    char szExePath[MAX_PATH] = "";
    if (GetFileNameFromProcessHandle(hProcess, szExePath))
      strcpy_s(pe32.szExeFile, szExePath);
    infoList->push_back(pe32);
    //
    char* cmdline;

    if (!dbggetwintext(winTextList, pe32.th32ProcessID))
      winTextList->push_back("");

    if (!dbggetcmdline(&cmdline, NULL, hProcess))
      commandList->push_back("ARG_GET_ERROR");
    else {
      cmdline_qoutes_placement_t posEnum = getqoutesplacement(cmdline);
      char* cmdLineExe = strstr(cmdline, pe32.szExeFile);
      size_t cmdLineExeSize = cmdLineExe ? strlen(pe32.szExeFile) : 0;

      if (!cmdLineExe) {
        char* exeName =
            strrchr(pe32.szExeFile, '\\')  ? strrchr(pe32.szExeFile, '\\') + 1
            : strrchr(pe32.szExeFile, '/') ? strrchr(pe32.szExeFile, '/') + 1
                                           : pe32.szExeFile;
        size_t exeNameLen = strlen(exeName);

        char* peNameInCmd = strstr(cmdline, exeName);
        // check for exe name is used in path to exe
        for (char* exeNameInCmdTmp = peNameInCmd; exeNameInCmdTmp;) {
          exeNameInCmdTmp = strstr(exeNameInCmdTmp + exeNameLen, exeName);
          if (!exeNameInCmdTmp) break;

          char* nextSlash =
              strchr(exeNameInCmdTmp, '\\')  ? strchr(exeNameInCmdTmp, '\\')
              : strchr(exeNameInCmdTmp, '/') ? strchr(exeNameInCmdTmp, '/')
                                             : NULL;
          if (nextSlash &&
              posEnum.posEnum ==
                  NO_QOUTES)  // if there NO_QOUTES, then the path to PE in
                              // cmdline can't contain spaces
          {
            if (strchr(exeNameInCmdTmp, ' ') <
                nextSlash)  // slash is in arguments
            {
              peNameInCmd = exeNameInCmdTmp;
              break;
            } else
              continue;
          } else if (nextSlash && posEnum.posEnum == QOUTES_AROUND_EXE) {
            if ((cmdline + posEnum.secondPos) <
                nextSlash)  // slash is in arguments
            {
              peNameInCmd = exeNameInCmdTmp;
              break;
            } else
              continue;
          } else {
            peNameInCmd = exeNameInCmdTmp;
            break;
          }
        }

        if (peNameInCmd)
          cmdLineExeSize =
              (size_t)(((LPBYTE)peNameInCmd - (LPBYTE)cmdline) + exeNameLen);
        else {
          // try to locate basic name, without extension
          Memory<char*> basicName(strlen(exeName) + 1,
                                  "dbglistprocesses:basicName");
          strncpy_s(basicName(), sizeof(char) * strlen(exeName) + 1, exeName,
                    _TRUNCATE);
          char* dotInName = strrchr(basicName(), '.');
          dotInName[0] = '\0';
          size_t basicNameLen = strlen(basicName());
          peNameInCmd = strstr(cmdline, basicName());
          // check for basic name is used in path to exe
          for (char* basicNameInCmdTmp = peNameInCmd; basicNameInCmdTmp;) {
            basicNameInCmdTmp =
                strstr(basicNameInCmdTmp + basicNameLen, basicName());
            if (!basicNameInCmdTmp) break;

            char* nextSlash = strchr(basicNameInCmdTmp, '\\')
                                  ? strchr(basicNameInCmdTmp, '\\')
                              : strchr(basicNameInCmdTmp, '/')
                                  ? strchr(basicNameInCmdTmp, '/')
                                  : NULL;
            if (nextSlash &&
                posEnum.posEnum ==
                    NO_QOUTES)  // if there NO_QOUTES, then the path to PE in
                                // cmdline can't contain spaces
            {
              if (strchr(basicNameInCmdTmp, ' ') <
                  nextSlash)  // slash is in arguments
              {
                peNameInCmd = basicNameInCmdTmp;
                break;
              } else
                continue;
            } else if (nextSlash && posEnum.posEnum == QOUTES_AROUND_EXE) {
              if ((cmdline + posEnum.secondPos) <
                  nextSlash)  // slash is in arguments
              {
                peNameInCmd = basicNameInCmdTmp;
                break;
              } else
                continue;
            } else {
              peNameInCmd = basicNameInCmdTmp;
              break;
            }
          }

          if (peNameInCmd)
            cmdLineExeSize = (size_t)(((LPBYTE)peNameInCmd - (LPBYTE)cmdline) +
                                      basicNameLen);
        }
      }

      switch (posEnum.posEnum) {
        case NO_CLOSE_QUOTE_FOUND:
          commandList->push_back(cmdline + cmdLineExeSize + 1);
          break;
        case NO_QOUTES:
          if (!posEnum.secondPos)
            commandList->push_back(cmdline + cmdLineExeSize);
          else
            commandList->push_back(cmdline +
                                   (cmdLineExeSize > posEnum.secondPos + 1
                                        ? cmdLineExeSize
                                        : posEnum.secondPos + 1));
          break;
        case QOUTES_AROUND_EXE:
          commandList->push_back(cmdline + cmdLineExeSize + 2);
          break;
        case QOUTES_AT_BEGIN_AND_END:
          cmdline[strlen(cmdline) - 1] = '\0';
          commandList->push_back(cmdline + cmdLineExeSize + 1);
          break;
      }

      if (!commandList->empty())
        commandList->back() = StringUtils::Trim(commandList->back());

      efree(cmdline);
    }
  } while (Process32Next(hProcessSnap, &pe32));
  return true;
}

static bool getcommandlineaddr(duint* addr, cmdline_error_t* cmd_line_error,
                               HANDLE hProcess = NULL) {
  duint pprocess_parameters;

  cmd_line_error->addr =
      (duint)GetPEBLocation(hProcess ? hProcess : fdProcessInfo->hProcess);

  if (cmd_line_error->addr == 0) {
    cmd_line_error->type = CMDL_ERR_GET_PEB;
    return false;
  }

  if (hProcess) {
    duint NumberOfBytesRead;
    if (!MemoryReadSafe(
            hProcess,
            (LPVOID)((cmd_line_error->addr) + offsetof(PEB, ProcessParameters)),
            &pprocess_parameters, sizeof(duint), &NumberOfBytesRead)) {
      cmd_line_error->type = CMDL_ERR_READ_PROCPARM_PTR;
      return false;
    }

    *addr = (pprocess_parameters) +
            offsetof(RTL_USER_PROCESS_PARAMETERS, CommandLine);
  } else {
    // cast-trick to calculate the address of the remote peb field
    // ProcessParameters
    cmd_line_error->addr =
        (duint) & (((PPEB)cmd_line_error->addr)->ProcessParameters);
    if (!MemRead(cmd_line_error->addr, &pprocess_parameters,
                 sizeof(pprocess_parameters))) {
      cmd_line_error->type = CMDL_ERR_READ_PEBBASE;
      return false;
    }

    *addr = (duint) &
            (((RTL_USER_PROCESS_PARAMETERS*)pprocess_parameters)->CommandLine);
  }
  return true;
}

static bool patchcmdline(duint getcommandline, duint new_command_line,
                         cmdline_error_t* cmd_line_error) {
  duint command_line_stored = 0;
  unsigned char data[100];

  cmd_line_error->addr = getcommandline;
  if (!MemRead(cmd_line_error->addr, &data, sizeof(data))) {
    cmd_line_error->type = CMDL_ERR_READ_GETCOMMANDLINEBASE;
    return false;
  }

#ifdef _WIN64
  /*
  00007FFC5B91E3C8 | 48 8B 05 19 1D 0E 00     | mov rax,qword ptr
  ds:[7FFC5BA000E8] 00007FFC5B91E3CF | C3                       | ret | This is
  a relative offset then to get the symbol: next instruction of getmodulehandle
  (+7 bytes) + offset to symbol (the last 4 bytes of the instruction)
  */
  if (data[0] != 0x48 || data[1] != 0x8B || data[2] != 0x05 ||
      data[7] != 0xC3) {
    cmd_line_error->type = CMDL_ERR_CHECK_GETCOMMANDLINESTORED;
    return false;
  }
  DWORD offset = *((DWORD*)&data[3]);
  command_line_stored = getcommandline + 7 + offset;
#else  // x86
  /*
  750FE9CA | A1 CC DB 1A 75           | mov eax,dword ptr ds:[751ADBCC] |
  750FE9CF | C3                       | ret |
  */
  if (data[0] != 0xA1 || data[5] != 0xC3) {
    cmd_line_error->type = CMDL_ERR_CHECK_GETCOMMANDLINESTORED;
    return false;
  }
  command_line_stored = *((duint*)&data[1]);
#endif

  // update the pointer in the debuggee
  if (!MemWrite(command_line_stored, &new_command_line,
                sizeof(new_command_line))) {
    cmd_line_error->addr = command_line_stored;
    cmd_line_error->type = CMDL_ERR_WRITE_GETCOMMANDLINESTORED;
    return false;
  }

  return true;
}

static bool fixgetcommandlinesbase(duint new_command_line_unicode,
                                   duint new_command_line_ascii,
                                   cmdline_error_t* cmd_line_error) {
  duint getcommandline;

  if (!valfromstring("kernelBase:GetCommandLineA", &getcommandline)) {
    if (!valfromstring("kernel32:GetCommandLineA", &getcommandline)) {
      cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE;
      return false;
    }
  }
  if (!patchcmdline(getcommandline, new_command_line_ascii, cmd_line_error))
    return false;

  if (!valfromstring("kernelbase:GetCommandLineW", &getcommandline)) {
    if (!valfromstring("kernel32:GetCommandLineW", &getcommandline)) {
      cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE;
      return false;
    }
  }
  if (!patchcmdline(getcommandline, new_command_line_unicode, cmd_line_error))
    return false;

  return true;
}

static std::vector<char> Utf16ToAnsi(const wchar_t* wstr) {
  std::vector<char> buffer;
  auto requiredSize =
      WideCharToMultiByte(CP_ACP, 0, wstr, -1, nullptr, 0, nullptr, nullptr);
  if (requiredSize > 0) {
    buffer.resize(requiredSize);
    WideCharToMultiByte(CP_ACP, 0, wstr, -1, &buffer[0], requiredSize, nullptr,
                        nullptr);
  }
  return buffer;
}

bool dbgsetcmdline(const char* cmd_line, cmdline_error_t* cmd_line_error) {
  // Make sure cmd_line_error is a valid pointer
  cmdline_error_t cmd_line_error_aux;
  if (cmd_line_error == NULL) cmd_line_error = &cmd_line_error_aux;

  // Get the command line address
  if (!getcommandlineaddr(&cmd_line_error->addr, cmd_line_error)) return false;
  auto command_line_addr = cmd_line_error->addr;

  // Convert the string to UTF-16
  auto command_linewstr = StringUtils::Utf8ToUtf16(cmd_line);
  if (command_linewstr.length() >=
      32766)  // 32766 is maximum character count for a null-terminated
              // UNICODE_STRING
    command_linewstr.resize(32766);
  // Convert the UTF-16 string to ANSI
  auto command_linestr = Utf16ToAnsi(command_linewstr.c_str());

  // Fill the UNICODE_STRING to be set in the debuggee
  UNICODE_STRING new_command_line;
  new_command_line.Length =
      USHORT(command_linewstr.length() *
             sizeof(WCHAR));  // max value: 32766 * 2 = 65532
  new_command_line.MaximumLength =
      new_command_line.Length + sizeof(WCHAR);  // max value: 65532 + 2 = 65534
  new_command_line.Buffer = PWSTR(
      command_linewstr.c_str());  // allow cast from const because the
                                  // UNICODE_STRING will not be used locally

  // Allocate remote memory for both the UNICODE_STRING.Buffer and the (null
  // terminated) ANSI buffer
  duint mem = MemAllocRemote(
      0, new_command_line.MaximumLength + command_linestr.size());
  if (!mem) {
    cmd_line_error->type = CMDL_ERR_ALLOC_UNICODEANSI_COMMANDLINE;
    return false;
  }

  // Write the UNICODE_STRING.Buffer to the debuggee (UNICODE_STRING.Length is
  // used because the remote memory is zeroed)
  if (!MemWrite(mem, new_command_line.Buffer, new_command_line.Length)) {
    cmd_line_error->addr = mem;
    cmd_line_error->type = CMDL_ERR_WRITE_UNICODE_COMMANDLINE;
    return false;
  }

  // Write the (null-terminated) ANSI buffer to the debuggee
  if (!MemWrite(mem + new_command_line.MaximumLength, command_linestr.data(),
                command_linestr.size())) {
    cmd_line_error->addr = mem + new_command_line.MaximumLength;
    cmd_line_error->type = CMDL_ERR_WRITE_ANSI_COMMANDLINE;
    return false;
  }

  // Change the pointers to the command line
  if (!fixgetcommandlinesbase(mem, mem + new_command_line.MaximumLength,
                              cmd_line_error))
    return false;

  // Put the remote buffer address in the UNICODE_STRING and write it to the PEB
  new_command_line.Buffer = PWSTR(mem);
  if (!MemWrite(command_line_addr, &new_command_line,
                sizeof(new_command_line))) {
    cmd_line_error->addr = command_line_addr;
    cmd_line_error->type = CMDL_ERR_WRITE_PEBUNICODE_COMMANDLINE;
    return false;
  }

  // Copy command line
  copyCommandLine(cmd_line);

  return true;
}

bool dbggetcmdline(char** cmd_line, cmdline_error_t* cmd_line_error,
                   HANDLE hProcess /* = NULL */) {
  UNICODE_STRING CommandLine;
  Memory<wchar_t*> wstr_cmd;
  cmdline_error_t cmd_line_error_aux;

  if (!cmd_line_error) cmd_line_error = &cmd_line_error_aux;

  if (!getcommandlineaddr(&cmd_line_error->addr, cmd_line_error, hProcess))
    return false;

  if (hProcess) {
    duint NumberOfBytesRead;
    if (!MemoryReadSafe(hProcess, (LPVOID)cmd_line_error->addr, &CommandLine,
                        sizeof(UNICODE_STRING), &NumberOfBytesRead)) {
      cmd_line_error->type = CMDL_ERR_READ_GETCOMMANDLINEBASE;
      return false;
    }

    wstr_cmd.realloc(CommandLine.Length + sizeof(wchar_t));

    cmd_line_error->addr = (duint)CommandLine.Buffer;
    if (!MemoryReadSafe(hProcess, (LPVOID)cmd_line_error->addr, wstr_cmd(),
                        CommandLine.Length, &NumberOfBytesRead)) {
      cmd_line_error->type = CMDL_ERR_GET_GETCOMMANDLINE;
      return false;
    }
  } else {
    if (!MemRead(cmd_line_error->addr, &CommandLine, sizeof(CommandLine))) {
      cmd_line_error->type = CMDL_ERR_READ_PROCPARM_PTR;
      return false;
    }

    wstr_cmd.realloc(CommandLine.Length + sizeof(wchar_t));

    cmd_line_error->addr = (duint)CommandLine.Buffer;
    if (!MemRead(cmd_line_error->addr, wstr_cmd(), CommandLine.Length)) {
      cmd_line_error->type = CMDL_ERR_READ_PROCPARM_CMDLINE;
      return false;
    }
  }
  SIZE_T wstr_cmd_size = wcslen(wstr_cmd()) + 1;
  SIZE_T cmd_line_size = wstr_cmd_size * 2;

  *cmd_line = (char*)emalloc(cmd_line_size, "dbggetcmdline:cmd_line");

  if (cmd_line_size <= 2) {
    *cmd_line[0] = '\0';
    return true;
  }

  // Convert TO UTF-8
  if (!WideCharToMultiByte(CP_UTF8, 0, wstr_cmd(), (int)wstr_cmd_size,
                           *cmd_line, (int)cmd_line_size, NULL, NULL)) {
    efree(*cmd_line);
    *cmd_line = nullptr;
    cmd_line_error->type = CMDL_ERR_CONVERTUNICODE;
    return false;
  }

  return true;
}

static DWORD WINAPI scriptThread(void* data) {
  CBPLUGINSCRIPT cbScript = (CBPLUGINSCRIPT)data;
  cbScript();
  return 0;
}

void dbgstartscriptthread(CBPLUGINSCRIPT cbScript) {
  CloseHandle(CreateThread(0, 0, scriptThread, (LPVOID)cbScript, 0, 0));
}

static void* InitDLLDebugW(const wchar_t* szFileName,
                           const wchar_t* szCommandLine,
                           const wchar_t* szCurrentFolder) {
  WString loaderFilename =
      StringUtils::sprintf(L"\\DLLLoader" ArchValue(L"32", L"64") L"_%04X.exe",
                           GetTickCount() & 0xFFFF);
  WString debuggeeLoaderPath = szFileName;
  {
    auto backslashIdx = debuggeeLoaderPath.rfind('\\');
    if (backslashIdx != WString::npos) debuggeeLoaderPath.resize(backslashIdx);
  }
  debuggeeLoaderPath += loaderFilename;
  WString loaderPath = StringUtils::Utf8ToUtf16(szDllLoaderPath);
  if (!CopyFileW(loaderPath.c_str(), debuggeeLoaderPath.c_str(), FALSE)) {
    debuggeeLoaderPath = StringUtils::Utf8ToUtf16(szProgramDir);
    debuggeeLoaderPath += loaderFilename;
    if (!CopyFileW(loaderPath.c_str(), debuggeeLoaderPath.c_str(), FALSE)) {
      dprintf(QT_TRANSLATE_NOOP(
          "DBG", "Error debugging DLL (failed to copy loader)\n"));
      return nullptr;
    }
  }

  PPROCESS_INFORMATION ReturnValue = (PPROCESS_INFORMATION)InitDebugW(
      debuggeeLoaderPath.c_str(), szCommandLine, szCurrentFolder);
  WString mappingName =
      StringUtils::sprintf(L"Local\\szLibraryName%X", ReturnValue->dwProcessId);
  const auto mappingSize = 512;
  DebugDLLFileMapping =
      CreateFileMappingW(INVALID_HANDLE_VALUE, 0, PAGE_READWRITE, 0,
                         mappingSize * sizeof(wchar_t), mappingName.c_str());
  if (DebugDLLFileMapping) {
    wchar_t* szLibraryPathMapping =
        (wchar_t*)MapViewOfFile(DebugDLLFileMapping, FILE_MAP_ALL_ACCESS, 0, 0,
                                mappingSize * sizeof(wchar_t));
    if (szLibraryPathMapping) {
      wcscpy_s(szLibraryPathMapping, mappingSize, szFileName);
      UnmapViewOfFile(szLibraryPathMapping);
    }
  }

  return ReturnValue;
}

static void debugLoopFunction(void* lpParameter, bool attach) {
  // initialize variables
  bIsAttached = attach;
  dbgsetskipexceptions(false);
  bFreezeStack = false;

  // prepare attach/createprocess
  DWORD pid;
  INIT_STRUCT* init;
  if (attach) {
    gInitExe = StringUtils::Utf8ToUtf16(szDebuggeePath);
    pid = DWORD(lpParameter);
    static PROCESS_INFORMATION pi_attached;
    memset(&pi_attached, 0, sizeof(pi_attached));
    fdProcessInfo = &pi_attached;
  } else {
    init = (INIT_STRUCT*)lpParameter;
    gInitExe = StringUtils::Utf8ToUtf16(init->exe);
    strcpy_s(szDebuggeePath, init->exe);
  }

  pDebuggedEntry = GetPE32DataW(gInitExe.c_str(), 0, UE_OEP);
  bEntryIsInMzHeader = pDebuggedEntry == 0 || pDebuggedEntry == 1;

  bFileIsDll = IsFileDLLW(StringUtils::Utf8ToUtf16(szDebuggeePath).c_str(), 0);
  if (bFileIsDll && !FileExists(szDllLoaderPath)) {
    dprintf(QT_TRANSLATE_NOOP("DBG",
                              "Error debugging DLL (loaddll.exe not found)\n"));
    return;
  }
  DbSetPath(nullptr, szDebuggeePath);

  if (!attach) {
    // Load command line if it exists in DB
    DbLoad(DbLoadSaveType::CommandLine);
    if (!isCmdLineEmpty()) {
      char* commandLineArguments = NULL;
      commandLineArguments = getCommandLineArgs();

      if (commandLineArguments && *commandLineArguments)
        init->commandline = commandLineArguments;
    }

    gInitCmd = StringUtils::Utf8ToUtf16(init->commandline);
    gInitDir = StringUtils::Utf8ToUtf16(init->currentfolder);

    // start the process
    if (bFileIsDll)
      fdProcessInfo = (PROCESS_INFORMATION*)InitDLLDebugW(
          gInitExe.c_str(), gInitCmd.c_str(), gInitDir.c_str());
    else
      fdProcessInfo = (PROCESS_INFORMATION*)InitDebugW(
          gInitExe.c_str(), gInitCmd.c_str(), gInitDir.c_str());
    if (!fdProcessInfo) {
      auto lastError = GetLastError();
      auto isElevated = BridgeIsProcessElevated();
      String error =
          stringformatinline(StringUtils::sprintf("{winerror@%d}", lastError));
      if (lastError == ERROR_ELEVATION_REQUIRED && !isElevated) {
        auto msg = StringUtils::Utf8ToUtf16(GuiTranslateText(
            QT_TRANSLATE_NOOP("DBG",
                              "The executable you are trying to debug requires "
                              "elevation. Restart as admin?")));
        auto title = StringUtils::Utf8ToUtf16(
            GuiTranslateText(QT_TRANSLATE_NOOP("DBG", "Elevation")));
        auto answer = MessageBoxW(GuiGetWindowHandle(), msg.c_str(),
                                  title.c_str(), MB_ICONQUESTION | MB_YESNO);
        wchar_t wszProgramPath[MAX_PATH] = L"";
        if (answer == IDYES && dbgrestartadmin()) {
          fdProcessInfo = &g_pi;
          GuiCloseApplication();
          return;
        }
      } else if (isElevated) {
        // This is most likely an application with uiAccess="true"
        // https://github.com/x64dbg/x64dbg/issues/1501
        // https://blogs.techsmith.com/inside-techsmith/devcorner-debug-uiaccess
        error += ", uiAccess=\"true\"";
      }
      fdProcessInfo = &g_pi;
      dprintf(QT_TRANSLATE_NOOP(
                  "DBG", "Error starting process (CreateProcess, %s)!\n"),
              error.c_str());
      return;
    }

    // check for WOW64
    BOOL wow64 = false, mewow64 = false;
    if (!IsWow64Process(fdProcessInfo->hProcess, &wow64) ||
        !IsWow64Process(GetCurrentProcess(), &mewow64)) {
      dputs(QT_TRANSLATE_NOOP("DBG", "IsWow64Process failed!"));
      StopDebug();
      return;
    }
    if ((mewow64 && !wow64) || (!mewow64 && wow64)) {
#ifdef _WIN64
      dputs(QT_TRANSLATE_NOOP("DBG", "Use x32dbg to debug this process!"));
#else
      dputs(QT_TRANSLATE_NOOP("DBG", "Use x64dbg to debug this process!"));
#endif  // _WIN64
      return;
    }

    // set script variables
    varset("$pid", fdProcessInfo->dwProcessId, true);

    if (!OpenProcessToken(fdProcessInfo->hProcess, TOKEN_ALL_ACCESS,
                          &hProcessToken))
      hProcessToken = 0;
  } else  // attach
  {
    gInitCmd.clear();
    gInitDir.clear();
  }

  // set custom handlers
  SetCustomHandler(UE_CH_CREATEPROCESS, (void*)cbCreateProcess);
  SetCustomHandler(UE_CH_EXITPROCESS, (void*)cbExitProcess);
  SetCustomHandler(UE_CH_CREATETHREAD, (void*)cbCreateThread);
  SetCustomHandler(UE_CH_EXITTHREAD, (void*)cbExitThread);
  SetCustomHandler(UE_CH_SYSTEMBREAKPOINT, (void*)cbSystemBreakpoint);
  SetCustomHandler(UE_CH_LOADDLL, (void*)cbLoadDll);
  SetCustomHandler(UE_CH_UNLOADDLL, (void*)cbUnloadDll);
  SetCustomHandler(UE_CH_OUTPUTDEBUGSTRING, (void*)cbOutputDebugString);
  SetCustomHandler(UE_CH_UNHANDLEDEXCEPTION, (void*)cbException);
  SetCustomHandler(UE_CH_DEBUGEVENT, (void*)cbDebugEvent);

  // inform GUI we started without problems
  GuiSetDebugState(initialized);
  GuiFocusView(GUI_DISASSEMBLY);
  GuiAddRecentFile(szDebuggeePath);

  // set GUI title
  strcpy_s(szBaseFileName, szDebuggeePath);
  int len = (int)strlen(szBaseFileName);
  while (szBaseFileName[len] != '\\' && len) len--;
  if (len) strcpy_s(szBaseFileName, szBaseFileName + len + 1);
  GuiUpdateWindowTitle(szBaseFileName);

  // call plugin callback
  PLUG_CB_INITDEBUG initInfo;
  initInfo.szFileName = szDebuggeePath;
  plugincbcall(CB_INITDEBUG, &initInfo);

  // call plugin callback (attach)
  if (attach) {
    PLUG_CB_ATTACH attachInfo;
    attachInfo.dwProcessId = (DWORD)pid;
    plugincbcall(CB_ATTACH, &attachInfo);
  }

  // Init program database
  DbLoad(DbLoadSaveType::DebugData);

  // run debug loop (returns when process debugging is stopped)
  if (attach) {
    if (AttachDebugger(pid, true, fdProcessInfo, (void*)cbAttachDebugger) ==
        false) {
      String error = stringformatinline(
          StringUtils::sprintf("{winerror@%d}", GetLastError()));
      dprintf(QT_TRANSLATE_NOOP(
                  "DBG", "Attach to process failed! GetLastError() = %s\n"),
              error.c_str());
    }
  } else {
    // close the process and thread handles we got back from CreateProcess, to
    // prevent duplicating the ones we will receive in cbCreateProcess
    CloseHandle(fdProcessInfo->hProcess);
    CloseHandle(fdProcessInfo->hThread);
    fdProcessInfo->hProcess = fdProcessInfo->hThread = nullptr;
    DebugLoop();
  }

  // fixes data loss when attach failed
  // (https://github.com/x64dbg/x64dbg/issues/1899)
  DbClose();

  // call plugin callback
  PLUG_CB_STOPDEBUG stopInfo;
  stopInfo.reserved = 0;
  plugincbcall(CB_STOPDEBUG, &stopInfo);

  // message the user/do final stuff
  RemoveAllBreakPoints(UE_OPTION_REMOVEALL);  // remove all breakpoints
  {
    EXCLUSIVE_ACQUIRE(LockDllBreakpoints);
    dllBreakpoints
        .clear();  // RemoveAllBreakPoints doesn't remove librarian breakpoints
  }

  // cleanup
  dbgcleartracestate();
  dbgClearRtuBreakpoints();
  ModClear();
  ThreadClear();
  WatchClear();
  TraceRecord.clear();
  _dbg_dbgenableRunTrace(false, nullptr);  // Stop run trace
  GuiSetDebugState(stopped);
  GuiUpdateAllViews();
  dputs(QT_TRANSLATE_NOOP("DBG", "Debugging stopped!"));
  fdProcessInfo->hProcess = fdProcessInfo->hThread = nullptr;
  varset("$hp", (duint)0, true);
  varset("$pid", (duint)0, true);
  if (hProcessToken) {
    CloseHandle(hProcessToken);
    hProcessToken = 0;
  }

  if (DebugDLLFileMapping) {
    CloseHandle(DebugDLLFileMapping);
    DebugDLLFileMapping = 0;
  }

  pDebuggedEntry = 0;
  pDebuggedBase = 0;
  pCreateProcessBase = 0;
  isDetachedByUser = false;
  hActiveThread = nullptr;
  if (!gDllLoader.empty())  // Delete the DLL loader (#1496)
  {
    DeleteFileW(gDllLoader.c_str());
    gDllLoader.clear();
  }
}

void dbgsetdebuggeeinitscript(const char* fileName) {
  if (fileName)
    strcpy_s(szDebuggeeInitializationScript, fileName);
  else
    szDebuggeeInitializationScript[0] = 0;
}

const char* dbggetdebuggeeinitscript() {
  return szDebuggeeInitializationScript;
}

void dbgsetforeground() {
  if (!bNoForegroundWindow) SetForegroundWindow(GuiGetWindowHandle());
}

DWORD WINAPI threadDebugLoop(void* lpParameter) {
  debugLoopFunction(lpParameter, false);
  return 0;
}

DWORD WINAPI threadAttachLoop(void* lpParameter) {
  debugLoopFunction(lpParameter, true);
  return 0;
}

bool dbgrestartadmin() {
  wchar_t wszProgramPath[MAX_PATH] = L"";
  if (GetModuleFileNameW(GetModuleHandleW(nullptr), wszProgramPath,
                         _countof(wszProgramPath))) {
    std::wstring file = wszProgramPath;
    auto last = wcsrchr(wszProgramPath, L'\\');
    if (last) *last = L'\0';
    // TODO: possibly escape characters in gInitCmd
    std::wstring params =
        L"\"" + gInitExe + L"\" \"" + gInitCmd + L"\" \"" + gInitDir + L"\"";
    auto result = ShellExecuteW(NULL, L"runas", file.c_str(), params.c_str(),
                                wszProgramPath, SW_SHOWDEFAULT);
    return int(result) > 32 && GetLastError() == ERROR_SUCCESS;
  }
  return false;
}

void StepIntoWow64(LPVOID traceCallBack) {
#ifndef _WIN64
  // NOTE: this workaround has the potential of detecting x64dbg while tracing,
  // disable it if that happens
  if (!bNoWow64SingleStepWorkaround) {
    unsigned char data[7];
    auto cip = GetContextDataEx(hActiveThread, UE_CIP);
    if (MemRead(cip, data, sizeof(data)) && data[0] == 0xEA &&
        data[5] == 0x33 && data[6] == 0x00)  // ljmp 33,XXXXXXXX
    {
      auto csp = GetContextDataEx(hActiveThread, UE_CSP);
      duint ret;
      if (MemRead(csp, &ret, sizeof(ret))) {
        SetBPX(ret, UE_SINGLESHOOT, traceCallBack);
        return;
      }
    }
  }
#endif  //_WIN64
  if (bPausedOnException && exceptionDispatchAddr &&
      !IsBPXEnabled(exceptionDispatchAddr)) {
    SetBPX(exceptionDispatchAddr, UE_SINGLESHOOT, traceCallBack);
  } else {
    StepInto(traceCallBack);
  }
}

void StepOverWrapper(LPVOID traceCallBack) {
  if (bPausedOnException && exceptionDispatchAddr &&
      !IsBPXEnabled(exceptionDispatchAddr)) {
    SetBPX(exceptionDispatchAddr, UE_SINGLESHOOT, traceCallBack);
  } else {
    StepOver(traceCallBack);
  }
}

bool dbgisdepenabled() {
  auto depEnabled = false;
#ifndef _WIN64
  typedef BOOL(WINAPI * GETPROCESSDEPPOLICY)(_In_ HANDLE /*hProcess*/,
                                             _Out_ LPDWORD /*lpFlags*/,
                                             _Out_ PBOOL /*lpPermanent*/
  );
  static auto GPDP = GETPROCESSDEPPOLICY(
      GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "GetProcessDEPPolicy"));
  if (GPDP) {
    // If you use fdProcessInfo->hProcess GetProcessDEPPolicy will put garbage
    // in bPermanent.
    auto hProcess = TitanOpenProcess(PROCESS_QUERY_INFORMATION, false,
                                     fdProcessInfo->dwProcessId);
    DWORD lpFlags;
    BOOL bPermanent;
    if (GPDP(hProcess, &lpFlags, &bPermanent)) depEnabled = lpFlags != 0;
    CloseHandle(hProcess);
  }
#else
  depEnabled = true;
#endif  //_WIN64
  return depEnabled;
}